From c32c29d97d25731fbb0f7006b9bed81bc8322984 Mon Sep 17 00:00:00 2001 From: Alexander Vanhee <160625516+AlexanderVanhee@users.noreply.github.com> Date: Fri, 18 Sep 2026 14:05:00 +0200 Subject: [PATCH] Add permission update system When fetching the updates, it now also creates app permission objects for the installed ref and the ref of the remote. These two objects are then diffed in some functions I basically copied from GNOME Software to produce a delta permission object. This delta permission object gets passed along with the entry to the update card, which uses it to show a warning icon. When running the update callback by pressing one of the update buttons, it now checks for these delta objects and shows a dialog via a fiber asking the user to confirm these additional permissions, the fiber way is similar how other confirmation dialogs get handled in Bazaar. The new dialog has designs for one app or multiple apps with additional permissions. I also reworked the update card in Vala to make it easier to reason about, but the actual functionality should be the same, except for the new icon of course. --- po/POTFILES.in | 3 +- src/bz-app-permissions.c | 168 +++++++++ src/bz-app-permissions.h | 4 + src/bz-application.c | 60 ++-- src/bz-flatpak-instance.c | 86 ++++- src/bz-install-controls.c | 11 +- src/bz-library-page.c | 2 +- src/bz-new-permissions-dialog.vala | 202 +++++++++++ src/bz-update-info.txt | 11 + src/bz-updates-card.blp | 5 - src/bz-updates-card.c | 505 --------------------------- src/bz-updates-card.h | 42 --- src/bz-updates-card.vala | 213 ++++++++++++ src/bz-window.c | 148 +++++--- src/bz.vapi | 75 ++++- src/meson.build | 4 +- src/safety-calculator.c | 524 +++++++++++++++-------------- src/safety-calculator.h | 5 + 18 files changed, 1167 insertions(+), 901 deletions(-) create mode 100644 src/bz-new-permissions-dialog.vala create mode 100644 src/bz-update-info.txt delete mode 100644 src/bz-updates-card.c delete mode 100644 src/bz-updates-card.h create mode 100644 src/bz-updates-card.vala diff --git a/po/POTFILES.in b/po/POTFILES.in index cdef6d9..87737eb 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -93,6 +93,7 @@ src/bz-login-page.blp src/bz-login-page.c src/bz-login-page.h src/bz-metainfo-preview.c +src/bz-new-permissions-dialog.vala src/bz-other-sources-list.vala src/bz-preferences-dialog.blp src/bz-preferences-dialog.c @@ -134,7 +135,7 @@ src/bz-transaction-tile.blp src/bz-transaction-tile.c src/bz-transaction.c src/bz-updates-card.blp -src/bz-updates-card.c +src/bz-updates-card.vala src/bz-update-history-dialog.blp src/bz-update-history-dialog.c src/bz-user-data-page.blp diff --git a/src/bz-app-permissions.c b/src/bz-app-permissions.c index fb1a2f8..0fb639a 100644 --- a/src/bz-app-permissions.c +++ b/src/bz-app-permissions.c @@ -105,6 +105,21 @@ static guint app_permissions_get_array_index (GPtrArray *array, const char *subpath); static guint get_strv_index (const gchar *const *strv, const gchar *value); +static gboolean array_contains_filesystem_path (GPtrArray *array, + BzFilesystemPathType type, + const char *subpath); +static int bus_policy_compare (const BzBusPolicy *policy1, + const BzBusPolicy *policy2); +static int bus_policy_compare_lopsided_with_permissions (const BzBusPolicy *policy1, + const BzBusPolicy *policy2); +static BzBusPolicy *bus_policy_copy (const BzBusPolicy *policy, + void *user_data); +static GPtrArray *ptr_array_diff (GPtrArray *array1, + GPtrArray *array2, + GCompareFunc compare_func, + GCopyFunc copy_func, + void *copy_func_data, + GDestroyNotify free_func); static void bz_app_permissions_finalize (GObject *object) @@ -322,6 +337,159 @@ bz_app_permissions_is_empty (BzAppPermissions *self) (self->bus_policies == NULL || self->bus_policies->len == 0)); } +static gboolean +array_contains_filesystem_path (GPtrArray *array, + BzFilesystemPathType type, + const char *subpath) +{ + if (array == NULL) + return FALSE; + + for (guint i = 0; i < array->len; i++) + { + const BzFilesystemPath *path = g_ptr_array_index (array, i); + if (path->type == type && g_strcmp0 (path->subpath, subpath) == 0) + return TRUE; + } + + return FALSE; +} + +static int +bus_policy_compare (const BzBusPolicy *policy1, + const BzBusPolicy *policy2) +{ + if (policy1->bus_type != policy2->bus_type) + return policy1->bus_type - policy2->bus_type; + + return strcmp (policy1->bus_name, policy2->bus_name); +} + +static int +bus_policy_compare_lopsided_with_permissions (const BzBusPolicy *policy1, + const BzBusPolicy *policy2) +{ + int compare_keys = bus_policy_compare (policy1, policy2); + + if (compare_keys != 0) + return compare_keys; + + if (policy2->permission > policy1->permission) + return 1; + + return 0; +} + +static BzBusPolicy * +bus_policy_copy (const BzBusPolicy *policy, + void *user_data) +{ + return bz_bus_policy_new (policy->bus_type, policy->bus_name, policy->permission); +} + +static GPtrArray * +ptr_array_diff (GPtrArray *array1, + GPtrArray *array2, + GCompareFunc compare_func, + GCopyFunc copy_func, + void *copy_func_data, + GDestroyNotify free_func) +{ + g_autoptr (GPtrArray) diff = NULL; + + if (array2 == NULL) + return NULL; + if (array1 == NULL) + return g_ptr_array_copy (array2, copy_func, copy_func_data); + + diff = g_ptr_array_new_with_free_func (free_func); + + for (unsigned int index1 = 0, index2 = 0; + index1 < array1->len || index2 < array2->len; + ) + { + const void *element1, *element2; + int comparison; + + if (index1 >= array1->len) + { + element2 = g_ptr_array_index (array2, index2); + comparison = 1; + } + else if (index2 >= array2->len) + { + element1 = g_ptr_array_index (array1, index1); + comparison = -1; + } + else + { + element1 = g_ptr_array_index (array1, index1); + element2 = g_ptr_array_index (array2, index2); + comparison = compare_func (element1, element2); + } + + if (comparison < 0) + { + index1++; + } + else if (comparison == 0) + { + index1++; + index2++; + } + else + { + g_ptr_array_add (diff, copy_func (element2, copy_func_data)); + index2++; + } + } + + return (diff->len > 0) ? g_steal_pointer (&diff) : NULL; +} + +// this is all from GNOME Software +BzAppPermissions * +bz_app_permissions_diff (BzAppPermissions *self, + BzAppPermissions *other) +{ + g_autoptr (BzAppPermissions) diff = bz_app_permissions_new (); + const GPtrArray *new_paths; + + g_return_val_if_fail (BZ_IS_APP_PERMISSIONS (self), NULL); + g_return_val_if_fail (self->is_sealed, NULL); + g_return_val_if_fail (BZ_IS_APP_PERMISSIONS (other), NULL); + g_return_val_if_fail (other->is_sealed, NULL); + + bz_app_permissions_set_flags (diff, other->flags & ~self->flags); + + new_paths = bz_app_permissions_get_filesystem_read (other); + for (guint i = 0; new_paths != NULL && i < new_paths->len; i++) + { + const BzFilesystemPath *path = g_ptr_array_index (new_paths, i); + + if (!array_contains_filesystem_path (self->filesystem_read, path->type, path->subpath)) + bz_app_permissions_add_filesystem_read (diff, path->type, path->subpath); + } + + new_paths = bz_app_permissions_get_filesystem_full (other); + for (guint i = 0; new_paths != NULL && i < new_paths->len; i++) + { + const BzFilesystemPath *path = g_ptr_array_index (new_paths, i); + + if (!array_contains_filesystem_path (self->filesystem_full, path->type, path->subpath)) + bz_app_permissions_add_filesystem_full (diff, path->type, path->subpath); + } + + diff->bus_policies = ptr_array_diff (self->bus_policies, other->bus_policies, + (GCompareFunc) bus_policy_compare_lopsided_with_permissions, + (GCopyFunc) bus_policy_copy, NULL, + (GDestroyNotify) bz_bus_policy_free); + + bz_app_permissions_seal (diff); + + return g_steal_pointer (&diff); +} + void bz_app_permissions_set_flags (BzAppPermissions *self, BzAppPermissionsFlags flags) diff --git a/src/bz-app-permissions.h b/src/bz-app-permissions.h index 5e714ff..f0dc329 100644 --- a/src/bz-app-permissions.h +++ b/src/bz-app-permissions.h @@ -187,6 +187,10 @@ const BzBusPolicy *const * bz_app_permissions_get_bus_policies (BzAppPermissions *self, size_t *out_n_bus_policies); +BzAppPermissions * +bz_app_permissions_diff (BzAppPermissions *self, + BzAppPermissions *other); + void bz_app_permissions_serialize (BzAppPermissions *self, GVariantBuilder *builder); diff --git a/src/bz-application.c b/src/bz-application.c index 8b3d284..ef0138c 100644 --- a/src/bz-application.c +++ b/src/bz-application.c @@ -63,6 +63,7 @@ #include "bz-serializable.h" #include "bz-state-info.h" #include "bz-transaction-manager.h" +#include "bz-update-info.h" #include "bz-window.h" #include "bz-yaml-parser.h" #include "dex-utils.h" @@ -2762,9 +2763,9 @@ fiber_replace_entry (BzApplication *self, static void fiber_check_for_updates (BzApplication *self) { - g_autoptr (GError) local_error = NULL; - g_autoptr (GPtrArray) update_ids = NULL; - GtkWindow *window = NULL; + g_autoptr (GError) local_error = NULL; + g_autoptr (GHashTable) update_ids = NULL; + GtkWindow *window = NULL; g_debug ("Checking for updates..."); bz_state_info_set_background_task_label (self->state, _ ("Checking for updates…")); @@ -2775,17 +2776,24 @@ fiber_check_for_updates (BzApplication *self) &local_error); window = gtk_application_get_active_window (GTK_APPLICATION (self)); if (update_ids != NULL && - update_ids->len > 0) + g_hash_table_size (update_ids) > 0) { - g_autoptr (GPtrArray) futures = NULL; - g_autoptr (GListStore) store = NULL; + g_autoptr (GPtrArray) unique_ids = NULL; + g_autoptr (GPtrArray) futures = NULL; + g_autoptr (GListStore) store = NULL; + GHashTableIter iter; + gpointer key = NULL; + gpointer value = NULL; - futures = g_ptr_array_new_with_free_func (dex_unref); - for (guint i = 0; i < update_ids->len; i++) + unique_ids = g_ptr_array_new (); + futures = g_ptr_array_new_with_free_func (dex_unref); + + g_hash_table_iter_init (&iter, update_ids); + while (g_hash_table_iter_next (&iter, &key, &value)) { - const char *unique_id = NULL; + const char *unique_id = key; - unique_id = g_ptr_array_index (update_ids, i); + g_ptr_array_add (unique_ids, (gpointer) unique_id); g_ptr_array_add (futures, bz_entry_cache_manager_get (self->cache, unique_id)); } @@ -2793,22 +2801,34 @@ fiber_check_for_updates (BzApplication *self) dex_future_allv ((DexFuture *const *) futures->pdata, futures->len), NULL); - store = g_list_store_new (BZ_TYPE_ENTRY); + store = g_list_store_new (BZ_TYPE_UPDATE_PERMISSION_INFO); for (guint i = 0; i < futures->len; i++) { - DexFuture *future = NULL; - const GValue *value = NULL; + DexFuture *future = NULL; + const GValue *value_out = NULL; + const char *unique_id = NULL; - future = g_ptr_array_index (futures, i); - value = dex_future_get_value (future, &local_error); + future = g_ptr_array_index (futures, i); + unique_id = g_ptr_array_index (unique_ids, i); + value_out = dex_future_get_value (future, &local_error); - if (value != NULL) - g_list_store_append (store, g_value_get_object (value)); + if (value_out != NULL) + { + g_autoptr (BzUpdatePermissionInfo) info = NULL; + BzAppPermissions *delta = NULL; + + delta = g_hash_table_lookup (update_ids, unique_id); + + info = g_object_new ( + BZ_TYPE_UPDATE_PERMISSION_INFO, + "entry", g_value_get_object (value_out), + "additional-permissions", delta, + NULL); + + g_list_store_append (store, info); + } else { - const char *unique_id = NULL; - - unique_id = g_ptr_array_index (update_ids, i); g_warning ("%s could not be resolved for the update list and thus will not be included: %s", unique_id, local_error->message); g_clear_pointer (&local_error, g_error_free); diff --git a/src/bz-flatpak-instance.c b/src/bz-flatpak-instance.c index 3e5d26f..77f002d 100644 --- a/src/bz-flatpak-instance.c +++ b/src/bz-flatpak-instance.c @@ -26,6 +26,7 @@ #include "config.h" +#include "bz-app-permissions.h" #include "bz-backend-notification.h" #include "bz-backend-transaction-op-payload.h" #include "bz-backend-transaction-op-progress-payload.h" @@ -1889,7 +1890,7 @@ retrieve_refs_for_remote_fiber (RetrieveRefsForRemoteData *data) #ifdef SANDBOXED_LIBFLATPAK if (is_noenumerate || - (installation == self->user && !is_whitelisted_user)) + (installation == self->user && !is_whitelisted_user)) #else if (is_noenumerate) #endif @@ -2012,7 +2013,7 @@ retrieve_updates_fiber (GatherRefsData *data) guint n_sys_refs = 0; g_autoptr (GPtrArray) user_refs = NULL; guint n_user_refs = 0; - g_autoptr (GPtrArray) ids = NULL; + g_autoptr (GHashTable) ids = NULL; bz_weak_get_or_return_reject (self, data->self); @@ -2046,33 +2047,94 @@ retrieve_updates_fiber (GatherRefsData *data) n_user_refs = user_refs->len; } - ids = g_ptr_array_new_with_free_func (g_free); + ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); for (guint i = 0; i < n_sys_refs + n_user_refs; i++) { - gboolean user = FALSE; - FlatpakInstalledRef *iref = NULL; + gboolean user = FALSE; + FlatpakInstalledRef *iref = NULL; + FlatpakInstallation *installation = NULL; + g_autofree char *unique_id = NULL; + const char *origin = NULL; + g_autoptr (GBytes) old_bytes = NULL; + g_autoptr (GKeyFile) old_keyfile = NULL; + g_autoptr (GBytes) new_bytes = NULL; + g_autoptr (GKeyFile) new_keyfile = NULL; + g_autoptr (BzAppPermissions) old_perm = NULL; + g_autoptr (BzAppPermissions) new_perm = NULL; + BzAppPermissions *delta = NULL; + g_autoptr (GError) perm_error = NULL; if (i < n_sys_refs) { - user = FALSE; - iref = g_ptr_array_index (system_refs, i); + user = FALSE; + iref = g_ptr_array_index (system_refs, i); + installation = self->system; } else { - user = TRUE; - iref = g_ptr_array_index (user_refs, i - n_sys_refs); + user = TRUE; + iref = g_ptr_array_index (user_refs, i - n_sys_refs); + installation = self->user; } if (should_skip_extension_ref (iref)) continue; - g_ptr_array_add (ids, - bz_flatpak_ref_format_unique (FLATPAK_REF (iref), user)); + unique_id = bz_flatpak_ref_format_unique (FLATPAK_REF (iref), user); + + old_bytes = flatpak_installed_ref_load_metadata (iref, cancellable, &perm_error); + if (old_bytes != NULL) + { + old_keyfile = g_key_file_new (); + if (!g_key_file_load_from_bytes (old_keyfile, old_bytes, G_KEY_FILE_NONE, NULL)) + g_clear_pointer (&old_keyfile, g_key_file_unref); + } + else + { + g_debug ("Failed to get installed metadata for '%s': %s", + flatpak_ref_get_name (FLATPAK_REF (iref)), perm_error->message); + } + g_clear_error (&perm_error); + + origin = flatpak_installed_ref_get_origin (iref); + if (old_keyfile != NULL && origin != NULL) + { + new_bytes = flatpak_installation_fetch_remote_metadata_sync ( + installation, origin, FLATPAK_REF (iref), cancellable, &perm_error); + if (new_bytes != NULL) + { + new_keyfile = g_key_file_new (); + if (!g_key_file_load_from_bytes (new_keyfile, new_bytes, G_KEY_FILE_NONE, NULL)) + g_clear_pointer (&new_keyfile, g_key_file_unref); + } + else + { + g_debug ("Failed to fetch remote metadata for '%s' from '%s': %s", + flatpak_ref_get_name (FLATPAK_REF (iref)), origin, perm_error->message); + } + } + g_clear_error (&perm_error); + + if (old_keyfile != NULL && new_keyfile != NULL) + { + old_perm = bz_app_permissions_new_from_metadata (old_keyfile, NULL); + new_perm = bz_app_permissions_new_from_metadata (new_keyfile, NULL); + } + + if (old_perm != NULL && new_perm != NULL) + delta = bz_app_permissions_diff (old_perm, new_perm); + else + { + delta = bz_app_permissions_new (); + bz_app_permissions_seal (delta); + } + + g_hash_table_replace (ids, g_steal_pointer (&unique_id), delta); } return dex_future_new_take_boxed ( - G_TYPE_PTR_ARRAY, g_steal_pointer (&ids)); + G_TYPE_HASH_TABLE, g_steal_pointer (&ids)); } static gboolean diff --git a/src/bz-install-controls.c b/src/bz-install-controls.c index f211d29..a4c3fd5 100644 --- a/src/bz-install-controls.c +++ b/src/bz-install-controls.c @@ -26,6 +26,7 @@ #include "bz-install-controls.h" #include "bz-state-info.h" +#include "bz-update-info.h" #include "progress-bar-designs/common.h" #include "template-callbacks.h" #include "util.h" @@ -190,11 +191,13 @@ find_matching_updates (BzInstallControls *self, for (guint i = 0; i < n_items; i++) { - g_autoptr (BzEntry) entry = NULL; - const char *id = NULL; + g_autoptr (BzUpdatePermissionInfo) info = NULL; + BzEntry *entry = NULL; + const char *id = NULL; - entry = g_list_model_get_item (available_updates, i); - id = bz_entry_get_id (entry); + info = g_list_model_get_item (available_updates, i); + entry = bz_update_permission_info_get_entry (info); + id = entry != NULL ? bz_entry_get_id (entry) : NULL; if (g_strcmp0 (id, group_id) == 0) g_list_store_append (store, entry); diff --git a/src/bz-library-page.c b/src/bz-library-page.c index 0100922..ba9ccb3 100644 --- a/src/bz-library-page.c +++ b/src/bz-library-page.c @@ -19,6 +19,7 @@ */ #include +#include #include "bz-entry-group.h" #include "bz-installed-tile.h" @@ -26,7 +27,6 @@ #include "bz-search-bar.h" #include "bz-section-view.h" #include "bz-transaction-tile.h" -#include "bz-updates-card.h" #include "template-callbacks.h" #include "util.h" diff --git a/src/bz-new-permissions-dialog.vala b/src/bz-new-permissions-dialog.vala new file mode 100644 index 0000000..6429607 --- /dev/null +++ b/src/bz-new-permissions-dialog.vala @@ -0,0 +1,202 @@ +/* + * bz-new-permissions-dialog.vala + * + * Copyright 2026 Alexander Vanhee + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +public class Bz.NewPermissionsDialog : Adw.Dialog { + private Dex.Promise promise; + private bool responded = false; + + private NewPermissionsDialog (Bz.UpdatePermissionInfo[] infos) { + Object (); + + content_width = 420; + title = _("New Permissions"); + + var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 12) { + margin_top = 12, + margin_bottom = 8, + margin_start = 18, + margin_end = 18, + }; + + if (infos.length == 1) + build_single_layout (box, infos[0]); + else + build_multi_layout (box, infos); + + var scrolled_window = new Gtk.ScrolledWindow () { + hscrollbar_policy = Gtk.PolicyType.NEVER, + vexpand = true, + propagate_natural_height = true, + child = box, + }; + + var confirm_button = new Gtk.Button.with_mnemonic (_("_Accept")) { + halign = Gtk.Align.CENTER, + margin_top = 12, + margin_bottom = 18, + }; + confirm_button.add_css_class ("suggested-action"); + confirm_button.add_css_class ("pill"); + confirm_button.clicked.connect (() => { + responded = true; + promise.resolve_boolean (true); + close (); + }); + + var toolbar_view = new Adw.ToolbarView (); + toolbar_view.add_top_bar (new Adw.HeaderBar () {show_title = infos.length > 1}); + toolbar_view.content = scrolled_window; + toolbar_view.add_bottom_bar (confirm_button); + + child = toolbar_view; + + promise = new Dex.Promise (); + + this.closed.connect (() => { + if (!responded) { + responded = true; + promise.resolve_boolean (false); + } + }); + } + + private void build_single_layout (Gtk.Box box, Bz.UpdatePermissionInfo info) { + var status_page = new Adw.StatusPage () { + vexpand = false, + }; + status_page.add_css_class ("compact"); + + var permissions_list = new Gtk.ListBox () { + selection_mode = Gtk.SelectionMode.NONE, + }; + permissions_list.add_css_class ("boxed-list"); + + var group = new Adw.PreferencesGroup (); + group.add (permissions_list); + + box.append (status_page); + box.append (group); + + status_page.paintable = info.entry.icon_paintable; + + uint n_items = populate_permissions_list (permissions_list, info); + + status_page.title = status_page.title = ngettext ( + "New Permission for %s", + "New Permissions for %s", + n_items + ).printf (info.entry.title); + status_page.description = ngettext ( + "This update requires the following additional permission", + "This update requires the following additional permissions", + n_items + ); + } + + private void build_multi_layout (Gtk.Box box, Bz.UpdatePermissionInfo[] infos) { + box.spacing = 24; + foreach (var info in infos) { + var entry = info.entry; + + var app_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 8); + + var header_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12) { + valign = Gtk.Align.CENTER, + margin_start = 8, + }; + + if (entry.icon_paintable != null) { + var icon = new Gtk.Image.from_paintable (entry.icon_paintable) { + pixel_size = 48, + }; + header_box.append (icon); + } + + var title_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0) { + valign = Gtk.Align.CENTER, + }; + + var title_label = new Gtk.Label (entry.title) { + halign = Gtk.Align.START, + xalign = 0, + }; + title_label.add_css_class ("heading"); + title_box.append (title_label); + + if (entry.developer != null) { + var developer_label = new Gtk.Label (entry.developer) { + halign = Gtk.Align.START, + xalign = 0, + }; + developer_label.add_css_class ("dim-label"); + title_box.append (developer_label); + } + + header_box.append (title_box); + + var permissions_list = new Gtk.ListBox () { + selection_mode = Gtk.SelectionMode.NONE, + }; + permissions_list.add_css_class ("boxed-list"); + populate_permissions_list (permissions_list, info); + + app_box.append (header_box); + app_box.append (permissions_list); + + box.append (app_box); + } + } + + private uint populate_permissions_list (Gtk.ListBox list, Bz.UpdatePermissionInfo info) { + var permissions = info.additional_permissions; + uint n_items = 0; + + if (permissions != null) { + var model = Bz.SafetyCalculator.analyze_permissions (permissions, false); + n_items = model.get_n_items (); + + for (int level = Bz.Importance.IMPORTANT; level >= Bz.Importance.UNIMPORTANT; level--) { + for (uint j = 0; j < n_items; j++) { + var row_data = (Bz.SafetyRow) model.get_item (j); + + if (row_data.importance != level) + continue; + + var row = Bz.context_row_new ( + row_data.icon_name, + row_data.importance, + row_data.title, + row_data.subtitle + ); + list.append (row); + } + } + } + + return n_items; + } + + public static Dex.Future present_and_wait (Gtk.Widget parent, Bz.UpdatePermissionInfo[] infos) { + var dialog = new NewPermissionsDialog (infos); + dialog.present (parent); + return dialog.promise; + } +} diff --git a/src/bz-update-info.txt b/src/bz-update-info.txt new file mode 100644 index 0000000..ed640cc --- /dev/null +++ b/src/bz-update-info.txt @@ -0,0 +1,11 @@ +prefix=bz +name=update_permission_info +parent-prefix=g +parent-name=object +author=AUTOGEN + +include="bz-entry.h" +include="bz-app-permissions.h" + +property=entry BzEntry BZ_TYPE_ENTRY object +property=additional_permissions BzAppPermissions BZ_TYPE_APP_PERMISSIONS object diff --git a/src/bz-updates-card.blp b/src/bz-updates-card.blp index f29c25e..862c79d 100644 --- a/src/bz-updates-card.blp +++ b/src/bz-updates-card.blp @@ -33,8 +33,3 @@ template $BzUpdatesCard: Adw.Bin { } }; } - -FilterListModel runtimes_filter_model { - filter: CustomFilter runtimes_filter {}; - model: bind template.state as <$BzStateInfo>.available-updates; -} \ No newline at end of file diff --git a/src/bz-updates-card.c b/src/bz-updates-card.c deleted file mode 100644 index 3c528bb..0000000 --- a/src/bz-updates-card.c +++ /dev/null @@ -1,505 +0,0 @@ -/* bz-updates-card.c - * - * Copyright 2026 Alexander Vanhee - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include - -#include "bz-entry.h" -#include "bz-release.h" -#include "bz-releases-list.h" -#include "bz-updates-card.h" -#include "template-callbacks.h" - -struct _BzUpdatesCard -{ - AdwBin parent_instance; - - BzStateInfo *state; - - /* Template widgets */ - AdwExpanderRow *expander_row; - GtkCustomFilter *runtimes_filter; - GtkFilterListModel *runtimes_filter_model; - - GPtrArray *app_rows; - GtkWidget *runtimes_row; - - gboolean has_closed; -}; - -G_DEFINE_FINAL_TYPE (BzUpdatesCard, bz_updates_card, ADW_TYPE_BIN) - -enum -{ - PROP_0, - PROP_STATE, - LAST_PROP -}; - -static GParamSpec *props[LAST_PROP] = { 0 }; - -enum -{ - SIGNAL_UPDATE, - LAST_SIGNAL, -}; - -static guint signals[LAST_SIGNAL]; - -static char *format_update_count (gpointer object, GListModel *updates); -static void update_all_cb (GtkButton *button, BzUpdatesCard *self); -static void update_runtimes_cb (GtkButton *button, BzUpdatesCard *self); -static void on_expander_expanded_cb (AdwExpanderRow *row, GParamSpec *pspec, BzUpdatesCard *self); -static gboolean filter_runtimes (BzEntry *entry, BzUpdatesCard *self); -static void on_available_updates_changed (BzUpdatesCard *self, GParamSpec *pspec, BzStateInfo *state); -static void on_runtimes_changed (GtkFilterListModel *model, guint position, guint removed, guint added, BzUpdatesCard *self); - -static void -on_update_single_cb (GtkButton *button, - BzEntry *entry) -{ - BzUpdatesCard *self = NULL; - g_autoptr (GListStore) store = NULL; - - self = BZ_UPDATES_CARD (gtk_widget_get_ancestor (GTK_WIDGET (button), BZ_TYPE_UPDATES_CARD)); - store = g_list_store_new (BZ_TYPE_ENTRY); - g_list_store_append (store, entry); - g_signal_emit (self, signals[SIGNAL_UPDATE], 0, store); -} - -static void -on_version_history_cb (GtkButton *button, - BzEntry *entry) -{ - GtkRoot *root = NULL; - g_autoptr (GListModel) history = NULL; - GtkWidget *dialog = NULL; - - root = gtk_widget_get_root (GTK_WIDGET (button)); - if (root == NULL) - return; - - g_object_get (entry, "version-history", &history, NULL); - dialog = bz_releases_dialog_new (history, NULL); - adw_dialog_present (ADW_DIALOG (dialog), GTK_WIDGET (root)); -} - -static GtkWidget * -build_app_row (BzEntry *entry, - BzUpdatesCard *self) -{ - AdwActionRow *row = NULL; - GtkWidget *icon = NULL; - GtkWidget *history_button = NULL; - GtkWidget *update_button = NULL; - GdkPaintable *paintable = NULL; - g_autoptr (GListModel) history = NULL; - g_autofree char *installed_ver = NULL; - const char *new_ver = NULL; // This will probably the same as the installed version if using cache... - g_autofree char *subtitle = NULL; - - row = ADW_ACTION_ROW (adw_action_row_new ()); - adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), - bz_entry_get_title (entry)); - - g_object_get (entry, "icon-paintable", &paintable, NULL); - icon = paintable != NULL - ? gtk_image_new_from_paintable (paintable) - : gtk_image_new_from_icon_name ("application-x-executable"); - g_clear_object (&paintable); - - gtk_image_set_pixel_size (GTK_IMAGE (icon), 48); - gtk_widget_set_size_request (icon, 48, 48); - gtk_widget_set_margin_top (icon, 6); - gtk_widget_set_margin_bottom (icon, 6); - gtk_widget_set_valign (icon, GTK_ALIGN_CENTER); - gtk_widget_add_css_class (icon, "icon-dropshadow"); - adw_action_row_add_prefix (row, icon); - - g_object_get (entry, - "version-history", &history, - "installed-version", &installed_ver, - NULL); - - if (history != NULL && g_list_model_get_n_items (history) > 0 && installed_ver != NULL) - { - g_autoptr (BzRelease) first = g_list_model_get_item (history, 0); - new_ver = bz_release_get_version (first); - - if (new_ver != NULL) - { - if (g_strcmp0 (installed_ver, new_ver) != 0) - subtitle = g_strdup_printf ("%s → %s", installed_ver, new_ver); - else - subtitle = g_strdup (new_ver); - adw_action_row_set_subtitle (row, subtitle); - } - } - - history_button = gtk_button_new_from_icon_name ("view-list-bullet-symbolic"); - gtk_widget_set_valign (history_button, GTK_ALIGN_CENTER); - gtk_widget_set_tooltip_text (history_button, _ ("Version History")); - gtk_widget_add_css_class (history_button, "flat"); - gtk_widget_set_visible (history_button, history != NULL && g_list_model_get_n_items (history) > 0); - g_signal_connect_data (history_button, "clicked", - G_CALLBACK (on_version_history_cb), - g_object_ref (entry), (GClosureNotify) g_object_unref, - 0); - adw_action_row_add_suffix (row, history_button); - - update_button = gtk_button_new_with_label (_ ("Update")); - gtk_widget_set_valign (update_button, GTK_ALIGN_CENTER); - g_signal_connect_data (update_button, "clicked", - G_CALLBACK (on_update_single_cb), - g_object_ref (entry), (GClosureNotify) g_object_unref, - 0); - adw_action_row_add_suffix (row, update_button); - return GTK_WIDGET (row); -} - -static GtkWidget * -build_runtimes_row (BzUpdatesCard *self) -{ - AdwActionRow *row = NULL; - GtkWidget *update_button = NULL; - - row = ADW_ACTION_ROW (adw_action_row_new ()); - gtk_widget_set_visible (GTK_WIDGET (row), FALSE); - - update_button = gtk_button_new_with_label (_ ("Update")); - gtk_widget_set_valign (update_button, GTK_ALIGN_CENTER); - g_signal_connect (update_button, "clicked", - G_CALLBACK (update_runtimes_cb), self); - adw_action_row_add_suffix (row, update_button); - - return GTK_WIDGET (row); -} - -static void -on_runtimes_changed (GtkFilterListModel *model, - guint position, - guint removed, - guint added, - BzUpdatesCard *self) -{ - guint n_items = 0; - g_autofree char *title = NULL; - - if (self->runtimes_row == NULL) - return; - - n_items = g_list_model_get_n_items (G_LIST_MODEL (model)); - gtk_widget_set_visible (self->runtimes_row, n_items > 0); - - if (n_items == 0) - return; - - title = g_strdup_printf (ngettext ("%u Runtime Update", "%u Runtime Updates", n_items), n_items); - adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self->runtimes_row), title); -} - -static void -repopulate_expander_row (BzUpdatesCard *self) -{ - GListModel *updates = NULL; - guint n_items = 0; - guint i = 0; - - for (i = 0; i < self->app_rows->len; i++) - adw_expander_row_remove (self->expander_row, g_ptr_array_index (self->app_rows, i)); - g_ptr_array_set_size (self->app_rows, 0); - - if (self->runtimes_row != NULL) - { - adw_expander_row_remove (self->expander_row, self->runtimes_row); - self->runtimes_row = NULL; - } - - if (self->state == NULL) - return; - - updates = bz_state_info_get_available_updates (self->state); - if (updates == NULL) - return; - - n_items = g_list_model_get_n_items (updates); - - for (i = 0; i < n_items; i++) - { - g_autoptr (BzEntry) entry = NULL; - GtkWidget *row = NULL; - - entry = g_list_model_get_item (updates, i); - - if (entry == NULL || !BZ_IS_ENTRY (entry)) - continue; - - if (!bz_entry_is_of_kinds (entry, BZ_ENTRY_KIND_APPLICATION)) - continue; - - row = build_app_row (entry, self); - adw_expander_row_add_row (self->expander_row, row); - g_ptr_array_add (self->app_rows, row); - } - - self->runtimes_row = build_runtimes_row (self); - adw_expander_row_add_row (self->expander_row, self->runtimes_row); - on_runtimes_changed (self->runtimes_filter_model, 0, 0, 0, self); - - if (!self->has_closed) - adw_expander_row_set_expanded (self->expander_row, n_items <= 3); -} - -static void -on_available_updates_changed (BzUpdatesCard *self, - GParamSpec *pspec, - BzStateInfo *state) -{ - gtk_filter_changed (GTK_FILTER (self->runtimes_filter), - GTK_FILTER_CHANGE_DIFFERENT); - repopulate_expander_row (self); -} - -static void -bz_updates_card_dispose (GObject *object) -{ - BzUpdatesCard *self = BZ_UPDATES_CARD (object); - - g_clear_object (&self->state); - g_clear_pointer (&self->app_rows, g_ptr_array_unref); - gtk_widget_dispose_template (GTK_WIDGET (self), BZ_TYPE_UPDATES_CARD); - - G_OBJECT_CLASS (bz_updates_card_parent_class)->dispose (object); -} - -static void -bz_updates_card_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - BzUpdatesCard *self = BZ_UPDATES_CARD (object); - - switch (prop_id) - { - case PROP_STATE: - g_value_set_object (value, bz_updates_card_get_state (self)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -bz_updates_card_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - BzUpdatesCard *self = BZ_UPDATES_CARD (object); - - switch (prop_id) - { - case PROP_STATE: - bz_updates_card_set_state (self, g_value_get_object (value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -bz_updates_card_class_init (BzUpdatesCardClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = bz_updates_card_dispose; - object_class->get_property = bz_updates_card_get_property; - object_class->set_property = bz_updates_card_set_property; - - props[PROP_STATE] = - g_param_spec_object ( - "state", - NULL, NULL, - BZ_TYPE_STATE_INFO, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); - - g_object_class_install_properties (object_class, LAST_PROP, props); - - signals[SIGNAL_UPDATE] = - g_signal_new ( - "update", - G_OBJECT_CLASS_TYPE (klass), - G_SIGNAL_RUN_FIRST, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__OBJECT, - G_TYPE_NONE, 1, - G_TYPE_LIST_MODEL); - g_signal_set_va_marshaller ( - signals[SIGNAL_UPDATE], - G_TYPE_FROM_CLASS (klass), - g_cclosure_marshal_VOID__OBJECTv); - - gtk_widget_class_set_template_from_resource (widget_class, "/io/github/kolunmi/Bazaar/bz-updates-card.ui"); - - bz_widget_class_bind_all_util_callbacks (widget_class); - gtk_widget_class_bind_template_child (widget_class, BzUpdatesCard, expander_row); - gtk_widget_class_bind_template_child (widget_class, BzUpdatesCard, runtimes_filter); - gtk_widget_class_bind_template_child (widget_class, BzUpdatesCard, runtimes_filter_model); - gtk_widget_class_bind_template_callback (widget_class, format_update_count); - gtk_widget_class_bind_template_callback (widget_class, update_all_cb); - gtk_widget_class_bind_template_callback (widget_class, on_expander_expanded_cb); -} - -static void -bz_updates_card_init (BzUpdatesCard *self) -{ - gtk_widget_init_template (GTK_WIDGET (self)); - - self->app_rows = g_ptr_array_new (); - - gtk_custom_filter_set_filter_func ( - self->runtimes_filter, (GtkCustomFilterFunc) filter_runtimes, - self, NULL); - - g_signal_connect (self->runtimes_filter_model, "items-changed", - G_CALLBACK (on_runtimes_changed), self); -} - -GtkWidget * -bz_updates_card_new (void) -{ - return g_object_new (BZ_TYPE_UPDATES_CARD, NULL); -} - -void -bz_updates_card_set_state (BzUpdatesCard *self, - BzStateInfo *state) -{ - g_return_if_fail (BZ_IS_UPDATES_CARD (self)); - g_return_if_fail (state == NULL || BZ_IS_STATE_INFO (state)); - - g_clear_object (&self->state); - if (state != NULL) - { - self->state = g_object_ref (state); - g_signal_connect_object (state, "notify::available-updates", - G_CALLBACK (on_available_updates_changed), - self, G_CONNECT_SWAPPED); - } - - gtk_filter_changed (GTK_FILTER (self->runtimes_filter), - GTK_FILTER_CHANGE_DIFFERENT); - - repopulate_expander_row (self); - - g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STATE]); -} - -BzStateInfo * -bz_updates_card_get_state (BzUpdatesCard *self) -{ - g_return_val_if_fail (BZ_IS_UPDATES_CARD (self), NULL); - return self->state; -} - -static char * -format_update_count (gpointer object, - GListModel *updates) -{ - guint n_updates = 0; - - if (updates == NULL) - return g_strdup (""); - - n_updates = g_list_model_get_n_items (updates); - - return g_strdup_printf (ngettext ("%u Available Update", - "%u Available Updates", - n_updates), - n_updates); -} - -static void -update_all_cb (GtkButton *button, - BzUpdatesCard *self) -{ - GListModel *updates = NULL; - - g_return_if_fail (GTK_IS_BUTTON (button)); - g_return_if_fail (BZ_IS_UPDATES_CARD (self)); - - if (self->state == NULL) - return; - - updates = bz_state_info_get_available_updates (self->state); - if (updates == NULL) - return; - - g_signal_emit (self, signals[SIGNAL_UPDATE], 0, updates); -} - -static void -update_runtimes_cb (GtkButton *button, - BzUpdatesCard *self) -{ - GListModel *runtimes = NULL; - g_autoptr (GListStore) store = NULL; - guint n_items = 0; - - runtimes = G_LIST_MODEL (self->runtimes_filter_model); - n_items = g_list_model_get_n_items (runtimes); - - if (n_items == 0) - return; - - store = g_list_store_new (BZ_TYPE_ENTRY); - - for (guint i = 0; i < n_items; i++) - { - g_autoptr (BzEntry) entry = g_list_model_get_item (runtimes, i); - if (entry != NULL) - g_list_store_append (store, entry); - } - - g_signal_emit (self, signals[SIGNAL_UPDATE], 0, store); -} - -static void -on_expander_expanded_cb (AdwExpanderRow *row, - GParamSpec *pspec, - BzUpdatesCard *self) -{ - if (!adw_expander_row_get_expanded (row)) - self->has_closed = TRUE; -} - -static gboolean -filter_runtimes (BzEntry *entry, - BzUpdatesCard *self) -{ - g_return_val_if_fail (BZ_IS_ENTRY (entry), FALSE); - - return bz_entry_is_of_kinds (entry, BZ_ENTRY_KIND_RUNTIME) || - bz_entry_is_of_kinds (entry, BZ_ENTRY_KIND_ADDON); -} diff --git a/src/bz-updates-card.h b/src/bz-updates-card.h deleted file mode 100644 index 2d62c2a..0000000 --- a/src/bz-updates-card.h +++ /dev/null @@ -1,42 +0,0 @@ -/* bz-updates-card.h - * - * Copyright 2026 Alexander Vanhee - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "bz-state-info.h" -#include - -G_BEGIN_DECLS - -#define BZ_TYPE_UPDATES_CARD (bz_updates_card_get_type ()) - -G_DECLARE_FINAL_TYPE (BzUpdatesCard, bz_updates_card, BZ, UPDATES_CARD, AdwBin) - -GtkWidget * -bz_updates_card_new (void); - -void -bz_updates_card_set_state (BzUpdatesCard *self, - BzStateInfo *state); - -BzStateInfo * -bz_updates_card_get_state (BzUpdatesCard *self); - -G_END_DECLS diff --git a/src/bz-updates-card.vala b/src/bz-updates-card.vala new file mode 100644 index 0000000..8a6abdb --- /dev/null +++ b/src/bz-updates-card.vala @@ -0,0 +1,213 @@ +/* bz-updates-card.vala + * + * Copyright 2026 Alexander Vanhee + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +[GtkTemplate (ui = "/io/github/kolunmi/Bazaar/bz-updates-card.ui")] +public class Bz.UpdatesCard : Adw.Bin { + private Bz.StateInfo? _state; + public Bz.StateInfo? state { + get { return _state; } + set { + _state = value; + if (_state != null) + _state.notify["available-updates"].connect ((s, p) => repopulate ()); + + repopulate (); + } + } + + [GtkChild] + private unowned Adw.ExpanderRow expander_row; + + private GenericArray app_rows = new GenericArray (); + private Gtk.Widget? runtimes_row = null; + private GenericArray runtime_entries = new GenericArray (); + private bool has_closed = false; + + public signal void update (ListModel entries); + + [GtkCallback] + private string format_update_count (ListModel? updates) { + if (updates == null) + return ""; + + uint n_updates = updates.get_n_items (); + return ngettext ("%u Available Update", "%u Available Updates", n_updates).printf (n_updates); + } + + [GtkCallback] + private void update_all_cb (Gtk.Button button) { + var updates = _state?.available_updates; + if (updates == null || updates.get_n_items () == 0) + return; + + var entries = new GenericArray (); + uint n_items = updates.get_n_items (); + for (uint i = 0; i < n_items; i++) { + var entry = (updates.get_item (i) as Bz.UpdatePermissionInfo)?.entry; + if (entry != null) + entries.add (entry); + } + emit_update (entries); + } + + [GtkCallback] + private void on_expander_expanded_cb (Object row, ParamSpec pspec) { + if (!expander_row.expanded) + has_closed = true; + } + + private void emit_update (GenericArray entries) { + var store = new ListStore (typeof (Bz.Entry)); + foreach (var entry in entries) + store.append (entry); + update (store); + } + + private void repopulate () { + foreach (var row in app_rows) + expander_row.remove (row); + if (runtimes_row != null) + expander_row.remove (runtimes_row); + + app_rows = new GenericArray (); + runtime_entries = new GenericArray (); + runtimes_row = null; + + var updates = _state?.available_updates; + if (updates == null) + return; + + uint n_items = updates.get_n_items (); + + for (uint i = 0; i < n_items; i++) { + var info = updates.get_item (i) as Bz.UpdatePermissionInfo; + var entry = info?.entry; + if (entry == null) + continue; + + if (entry.is_of_kinds (Bz.EntryKind.APPLICATION)) { + var row = build_app_row (info); + expander_row.add_row (row); + app_rows.add (row); + } else if (entry.is_of_kinds (Bz.EntryKind.RUNTIME) || + entry.is_of_kinds (Bz.EntryKind.ADDON)) { + runtime_entries.add (entry); + } + } + + runtimes_row = build_runtimes_row (); + expander_row.add_row (runtimes_row); + + if (!has_closed) + expander_row.expanded = n_items <= 3; + } + + private Gtk.Widget build_app_row (Bz.UpdatePermissionInfo info) { + var entry = info.entry; + var delta = info.additional_permissions; + + var row = new Adw.ActionRow () { + title = entry.title, + }; + + var icon = new Gtk.Image () { + pixel_size = 48, + valign = Gtk.Align.CENTER, + margin_top = 6, + margin_bottom = 6, + }; + if (entry.icon_paintable != null) + icon.paintable = entry.icon_paintable; + else + icon.icon_name = "application-x-executable"; + icon.add_css_class ("icon-dropshadow"); + row.add_prefix (icon); + + ListModel? history = entry.version_history; + string? installed_ver = entry.installed_version; + bool has_history = history != null && history.get_n_items () > 0; + + if (has_history && installed_ver != null) { + string? new_ver = ((Bz.Release) history.get_item (0)).version; + + if (new_ver != null) + row.subtitle = installed_ver != new_ver + ? "%s → %s".printf (installed_ver, new_ver) + : new_ver; + } + + if (delta != null && !delta.is_empty ()) { + var permissions_icon = new Gtk.Image.from_icon_name ("permissions-warning-symbolic") { + valign = Gtk.Align.CENTER, + tooltip_text = _("This update requests new permissions"), + }; + permissions_icon.add_css_class ("warning"); + row.add_suffix (permissions_icon); + } + + var history_button = new Gtk.Button.from_icon_name ("view-list-bullet-symbolic") { + valign = Gtk.Align.CENTER, + tooltip_text = _("Version History"), + visible = has_history, + }; + history_button.add_css_class ("flat"); + history_button.clicked.connect (() => { + var dialog = Bz.ReleasesList.dialog_new (entry.version_history, null); + ((Adw.Dialog) dialog).present (this.get_root ()); + }); + + row.add_suffix (history_button); + + var update_button = new Gtk.Button.with_label (_("Update")) { + valign = Gtk.Align.CENTER, + }; + update_button.clicked.connect (() => { + var entries = new GenericArray (); + entries.add (info.entry); + emit_update (entries); + }); + row.add_suffix (update_button); + + return row; + } + + private Gtk.Widget build_runtimes_row () { + uint n_items = runtime_entries.length; + + var row = new Adw.ActionRow () { + visible = n_items > 0, + }; + + if (n_items > 0) + row.title = ngettext ("%u Runtime Update", "%u Runtime Updates", n_items).printf (n_items); + + var update_button = new Gtk.Button.with_label (_("Update")) { + valign = Gtk.Align.CENTER, + }; + update_button.clicked.connect (() => { + if (runtime_entries.length == 0) + return; + emit_update (runtime_entries); + }); + row.add_suffix (update_button); + + return row; + } +} diff --git a/src/bz-window.c b/src/bz-window.c index 070c270..f64788b 100644 --- a/src/bz-window.c +++ b/src/bz-window.c @@ -21,8 +21,8 @@ // This file is an utter mess #include "config.h" -#include #include +#include #include "bz-addons-dialog.h" #include "bz-application.h" @@ -42,6 +42,7 @@ #include "bz-transaction-manager.h" #include "bz-update-history-data-point.h" #include "bz-update-history-dialog.h" +#include "bz-update-info.h" #include "bz-user-data-page.h" #include "bz-window.h" #include "env.h" @@ -103,6 +104,10 @@ BZ_DEFINE_DATA ( BZ_RELEASE_DATA (group, g_object_unref); BZ_RELEASE_DATA (source, g_object_unref)) +static DexFuture * +update_fiber (BzWeakRef *wr, + GListModel *entries); + static DexFuture * transact_fiber (TransactData *data); @@ -215,68 +220,119 @@ update_cb (BzWindow *self, GListModel *entries, GtkWidget *widget) { - g_autoptr (BzTransaction) transaction = NULL; - guint n_updates = 0; - g_autofree BzEntry **updates_buf = NULL; - GListModel *available_updates = NULL; + g_autoptr (BzWeakRef) wr = NULL; - g_return_if_fail (BZ_IS_WINDOW (self)); - g_return_if_fail (G_IS_LIST_MODEL (entries)); - - n_updates = g_list_model_get_n_items (entries); - if (n_updates == 0) + if (g_list_model_get_n_items (entries) == 0) return; - updates_buf = g_malloc_n (n_updates, sizeof (*updates_buf)); - for (guint i = 0; i < n_updates; i++) - updates_buf[i] = g_list_model_get_item (entries, i); + wr = bz_weak_ref_new (self); - transaction = bz_transaction_new_full ( - NULL, 0, - updates_buf, n_updates, - NULL, 0); + dex_future_disown (dex_scheduler_spawnv ( + dex_scheduler_get_default (), bz_get_dex_stack_size (), + G_CALLBACK (update_fiber), + 2, BZ_TYPE_WEAK_REF, wr, G_TYPE_OBJECT, entries)); +} + +static DexFuture * +update_fiber (BzWeakRef *wr, + GListModel *entries) +{ + g_autoptr (BzWindow) self = NULL; + g_autoptr (BzTransaction) transaction = NULL; + g_autoptr (GPtrArray) updates = NULL; + g_autoptr (GHashTable) update_ids = NULL; + GListModel *available_updates = NULL; + g_autoptr (GPtrArray) permission_infos = NULL; + guint n_updates = 0; + + bz_weak_get_or_return_reject (self, &wr->ref); + + n_updates = g_list_model_get_n_items (entries); + updates = g_ptr_array_new_full (n_updates, g_object_unref); + update_ids = g_hash_table_new (g_str_hash, g_str_equal); + for (guint i = 0; i < n_updates; i++) + { + BzEntry *entry = NULL; + + entry = g_list_model_get_item (entries, i); + g_ptr_array_add (updates, entry); + g_hash_table_add (update_ids, (gpointer) bz_entry_get_id (entry)); + } + + available_updates = bz_state_info_get_available_updates (self->state); + + permission_infos = g_ptr_array_new_with_free_func (g_object_unref); + if (available_updates != NULL) + { + guint n_available = 0; + + n_available = g_list_model_get_n_items (available_updates); + + for (guint i = 0; i < n_available; i++) + { + g_autoptr (BzUpdatePermissionInfo) info = NULL; + BzEntry *info_entry = NULL; + const char *info_id = NULL; + BzAppPermissions *delta = NULL; + + info = g_list_model_get_item (available_updates, i); + info_entry = bz_update_permission_info_get_entry (info); + info_id = bz_entry_get_id (info_entry); + delta = bz_update_permission_info_get_additional_permissions (info); + + if (delta == NULL || bz_app_permissions_is_empty (delta)) + continue; + + if (g_hash_table_contains (update_ids, info_id)) + g_ptr_array_add (permission_infos, g_object_ref (info)); + } + } + + if (permission_infos->len > 0) + { + gboolean confirmed = FALSE; + + confirmed = dex_await_boolean ( + bz_new_permissions_dialog_present_and_wait ( + GTK_WIDGET (self), + (BzUpdatePermissionInfo **) permission_infos->pdata, + permission_infos->len), + NULL); + + if (!confirmed) + return dex_future_new_false (); + } + + transaction = bz_transaction_new_full (NULL, 0, (BzEntry **) updates->pdata, + updates->len, NULL, 0); dex_future_disown (bz_transaction_manager_add ( bz_state_info_get_transaction_manager (self->state), transaction)); - available_updates = bz_state_info_get_available_updates (self->state); if (G_IS_LIST_STORE (available_updates)) { - GListStore *store = G_LIST_STORE (available_updates); - guint n_available = g_list_model_get_n_items (available_updates); + GListStore *store = NULL; + guint n_available = 0; + + store = G_LIST_STORE (available_updates); + n_available = g_list_model_get_n_items (available_updates); for (guint i = n_available; i > 0; i--) { - guint current_size = 0; - guint idx = 0; - g_autoptr (BzEntry) available_entry = NULL; - const char *available_id = NULL; + g_autoptr (BzUpdatePermissionInfo) available_info = NULL; + const char *available_id = NULL; - idx = i - 1; - current_size = g_list_model_get_n_items (available_updates); + available_info = g_list_model_get_item (available_updates, i - 1); + available_id = bz_entry_get_id (bz_update_permission_info_get_entry (available_info)); - if (idx >= current_size) - continue; - - available_entry = g_list_model_get_item (available_updates, idx); - available_id = bz_entry_get_id (available_entry); - - for (guint j = 0; j < n_updates; j++) - { - if (g_strcmp0 (available_id, bz_entry_get_id (updates_buf[j])) == 0) - { - g_list_store_remove (store, idx); - break; - } - } + if (g_hash_table_contains (update_ids, available_id)) + g_list_store_remove (store, i - 1); } } g_object_notify (G_OBJECT (self->state), "available-updates"); - - for (guint i = 0; i < n_updates; i++) - g_object_unref (updates_buf[i]); + return dex_future_new_true (); } static void @@ -785,7 +841,7 @@ open_uri_finish_cb (GObject *source, gpointer user_data) { g_autoptr (BzWindow) self = BZ_WINDOW (user_data); - GtkUriLauncher *launcher = GTK_URI_LAUNCHER (source); + GtkUriLauncher *launcher = GTK_URI_LAUNCHER (source); g_autoptr (GError) local_error = NULL; if (!gtk_uri_launcher_launch_finish (launcher, result, &local_error) && @@ -798,8 +854,8 @@ action_open_uri (GtkWidget *widget, const char *action_name, GVariant *parameter) { - BzWindow *self = BZ_WINDOW (widget); - const char *uri = g_variant_get_string (parameter, NULL); + BzWindow *self = BZ_WINDOW (widget); + const char *uri = g_variant_get_string (parameter, NULL); g_autoptr (GtkUriLauncher) launcher = NULL; if (uri == NULL || *uri == '\0') diff --git a/src/bz.vapi b/src/bz.vapi index 31d654a..a1e2b9d 100644 --- a/src/bz.vapi +++ b/src/bz.vapi @@ -47,6 +47,9 @@ public extern class Bz.AllAppsPage : GLib.Object { public extern class Bz.AppPermissions : GLib.Object { [CCode (has_construct_function = false)] protected AppPermissions (); + + [CCode (cname = "bz_app_permissions_is_empty")] + public bool is_empty (); } [CCode (cheader_filename = "bz-app-size-dialog.h", type_id = "bz_app_size_dialog_get_type ()")] @@ -159,10 +162,10 @@ public extern class Bz.ContentProvider : GLib.Object { protected ContentProvider (); } -[CCode (cheader_filename = "bz-context-row.h", type_id = "bz_context_row_get_type ()")] -public extern class Bz.ContextRow : GLib.Object { - [CCode (has_construct_function = false)] - protected ContextRow (); +[CCode (cheader_filename = "bz-context-row.h")] +namespace Bz { + [CCode (cname = "bz_context_row_new")] + public unowned Adw.ActionRow context_row_new (string? icon_name, Bz.Importance importance, string title, string? subtitle); } [CCode (cheader_filename = "bz-context-tile.h", type_id = "bz_context_tile_get_type ()")] @@ -239,6 +242,14 @@ public extern class Bz.EntryInspector : GLib.Object { protected EntryInspector (); } +[Flags] +[CCode (cheader_filename = "bz-entry.h", cname = "BzEntryKind", cprefix = "BZ_ENTRY_KIND_", has_type_id = false)] +public enum Bz.EntryKind { + APPLICATION, + RUNTIME, + ADDON, +} + [CCode (cheader_filename = "bz-entry.h", type_id = "bz_entry_get_type ()")] public extern class Bz.Entry : GLib.Object { [CCode (has_construct_function = false)] @@ -246,7 +257,16 @@ public extern class Bz.Entry : GLib.Object { public string? unique_id { get; set; } + public string? title { get; } + public string? developer { get; } + public Gdk.Paintable? icon_paintable { get; } + [NoAccessorMethod] + public GLib.ListModel? version_history { owned get; } + public string? installed_version { get; } public Bz.Repository? get_repository (GLib.ListModel repos); + + [CCode (cname = "bz_entry_is_of_kinds")] + public bool is_of_kinds (Bz.EntryKind kinds); } [CCode (cheader_filename = "bz-error-dialog.h", type_id = "bz_error_dialog_get_type ()")] @@ -453,6 +473,9 @@ public extern class Bz.ProgressBar : GLib.Object { public extern class Bz.ReleasesList : GLib.Object { [CCode (has_construct_function = false)] protected ReleasesList (); + + [CCode (cname = "bz_releases_dialog_new")] + public static unowned Gtk.Widget dialog_new (GLib.ListModel? version_history, GLib.ListModel? installed_versions); } [CCode (cheader_filename = "bz-result.h", type_id = "bz_result_get_type ()")] @@ -599,18 +622,21 @@ public extern class Bz.Transaction : GLib.Object { protected Transaction (); } +[CCode (cheader_filename = "bz-update-info.h", type_id = "bz_update_permission_info_get_type ()")] +public extern class Bz.UpdatePermissionInfo : GLib.Object { + [CCode (has_construct_function = false)] + protected UpdatePermissionInfo (); + + public Bz.Entry? entry { get; set; } + public Bz.AppPermissions? additional_permissions { get; set;} +} + [CCode (cheader_filename = "bz-update-history-dialog.h", type_id = "bz_update_history_dialog_get_type ()")] public extern class Bz.UpdateHistoryDialog : GLib.Object { [CCode (has_construct_function = false)] protected UpdateHistoryDialog (); } -[CCode (cheader_filename = "bz-updates-card.h", type_id = "bz_updates_card_get_type ()")] -public extern class Bz.UpdatesCard : GLib.Object { - [CCode (has_construct_function = false)] - protected UpdatesCard (); -} - [CCode (cheader_filename = "bz-user-data-page.h", type_id = "bz_user_data_page_get_type ()")] public extern class Bz.UserDataPage : GLib.Object { [CCode (has_construct_function = false)] @@ -896,6 +922,8 @@ public extern class Bz.PrideFlagStripeSpec : GLib.Object { public extern class Bz.Release : GLib.Object { [CCode (has_construct_function = false)] protected Release (); + + public string? version { get; set; } } [CCode (cheader_filename = "bz-repository.h", type_id = "bz_repository_get_type ()")] @@ -922,10 +950,36 @@ public extern class Bz.RootCuratedConfig : GLib.Object { protected RootCuratedConfig (); } +[CCode (cheader_filename = "safety-calculator.h", cname = "BzImportance", cprefix = "BZ_IMPORTANCE_", has_type_id = false)] +public enum Bz.Importance { + UNIMPORTANT, + NEUTRAL, + INFORMATION, + WARNING, + IMPORTANT, +} + +[CCode (cheader_filename = "safety-calculator.h")] +namespace Bz.SafetyCalculator { + [CCode (cname = "bz_safety_calculator_analyze_permissions")] + public static GLib.ListModel analyze_permissions (Bz.AppPermissions? permissions, bool include_no_access_rows); + + [CCode (cname = "bz_safety_calculator_analyze_entry")] + public GLib.ListModel analyze_entry (Bz.Entry entry); + + [CCode (cname = "bz_safety_calculator_calculate_rating")] + public Bz.Importance calculate_rating (Bz.Entry entry); +} + [CCode (cheader_filename = "bz-safety-row.h", type_id = "bz_safety_row_get_type ()")] public extern class Bz.SafetyRow : GLib.Object { [CCode (has_construct_function = false)] protected SafetyRow (); + + public Bz.Importance importance { get; set;} + public string? icon_name { get; set;} + public string? title { get; set;} + public string? subtitle { get; set;} } [CCode (cheader_filename = "bz-search-bias.h", type_id = "bz_search_bias_get_type ()")] @@ -955,6 +1009,7 @@ public extern class Bz.StateInfo : GLib.Object { public static unowned Bz.StateInfo get_default (); public GLib.ListModel? repositories { get; } + public GLib.ListModel? available_updates { get; set; } } [CCode (cheader_filename = "bz-transact-icon-info.h", type_id = "bz_transact_icon_info_get_type ()")] diff --git a/src/meson.build b/src/meson.build index f3ea65b..93aa08c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -152,7 +152,6 @@ bz_sources = files( 'bz-transaction-manager.c', 'bz-transaction-tile.c', 'bz-transaction.c', - 'bz-updates-card.c', 'bz-update-history-dialog.c', 'bz-user-data-page.c', 'bz-user-data-tile.c', @@ -264,6 +263,7 @@ gobject_specs = files( 'bz-size-result.txt', 'bz-state-info.txt', 'bz-update-history-data-point.txt', + 'bz-update-info.txt', 'bz-transact-icon-info.txt', 'bz-transaction-dialog-result.txt', 'bz-transaction-entry-tracker.txt', @@ -407,8 +407,10 @@ vala_sources = files( 'bz-cosmic-place-applet-dialog.vala', 'bz-decorated-screenshot.vala', 'bz-themed-rect.vala', + 'bz-updates-card.vala', 'bz-flathub-curated-section.vala', 'bz-other-sources-list.vala', + 'bz-new-permissions-dialog.vala', ) vala_args = [ diff --git a/src/safety-calculator.c b/src/safety-calculator.c index fb0e1ff..e97f4ef 100644 --- a/src/safety-calculator.c +++ b/src/safety-calculator.c @@ -49,13 +49,11 @@ lookup_well_known_bus_policy (const char *bus_name, const char **out_description); GListModel * -bz_safety_calculator_analyze_entry (BzEntry *entry) +bz_safety_calculator_analyze_permissions (BzAppPermissions *permissions, + gboolean include_no_access_rows) { GListStore *store = NULL; - BzAppPermissions *permissions = NULL; BzAppPermissionsFlags perm_flags = BZ_APP_PERMISSIONS_FLAGS_NONE; - gboolean is_verified = FALSE; - gboolean is_foss = FALSE; const GPtrArray *filesystem_read = NULL; const GPtrArray *filesystem_full = NULL; const BzBusPolicy *const *bus_policies = NULL; @@ -64,13 +62,8 @@ bz_safety_calculator_analyze_entry (BzEntry *entry) BzImportance fs_importance = BZ_IMPORTANCE_WARNING; guint i = 0; - g_return_val_if_fail (BZ_IS_ENTRY (entry), NULL); + store = g_list_store_new (BZ_TYPE_SAFETY_ROW); - store = g_list_store_new (BZ_TYPE_SAFETY_ROW); - is_verified = bz_entry_is_verified (entry); - is_foss = bz_entry_get_is_foss (entry); - - g_object_get (entry, "permissions", &permissions, NULL); if (permissions != NULL) perm_flags = bz_app_permissions_get_flags (permissions); @@ -87,264 +80,289 @@ bz_safety_calculator_analyze_entry (BzEntry *entry) _ ("Unknown Permissions"), _ ("Permissions are missing for this app."), NULL, NULL, NULL); + return G_LIST_MODEL (store); } - else + + filesystem_read = bz_app_permissions_get_filesystem_read (permissions); + filesystem_full = bz_app_permissions_get_filesystem_full (permissions); + bus_policies = bz_app_permissions_get_bus_policies (permissions, &n_bus_policies); + + if (include_no_access_rows) + add_row_if_permission (store, + bz_app_permissions_is_empty (permissions), + BZ_IMPORTANCE_UNIMPORTANT, + "permissions-sandboxed-symbolic", + _ ("No Permissions"), + _ ("App is fully sandboxed"), + NULL, NULL, NULL); + if (include_no_access_rows) + add_row_if_permission (store, + (perm_flags & BZ_APP_PERMISSIONS_FLAGS_NETWORK) != 0, + BZ_IMPORTANCE_INFORMATION, + "network-wireless-symbolic", + _ ("Network Access"), + _ ("Can access the internet"), + "network-wireless-disabled-symbolic", + _ ("No Network Access"), + _ ("Cannot access the internet")); + + if ((perm_flags & BZ_APP_PERMISSIONS_FLAGS_DEVICES) || include_no_access_rows) + add_row_if_permission (store, + (perm_flags & BZ_APP_PERMISSIONS_FLAGS_DEVICES) != 0, + (perm_flags & BZ_APP_PERMISSIONS_FLAGS_NETWORK) ? BZ_IMPORTANCE_WARNING : BZ_IMPORTANCE_INFORMATION, + "camera-photo-symbolic", + _ ("User Device Access"), + _ ("Can access devices such as webcams or gaming controllers"), + "camera-disabled-symbolic", + _ ("No User Device Access"), + _ ("Cannot access devices such as webcams or gaming controllers")); + add_row_if_permission (store, + (perm_flags & BZ_APP_PERMISSIONS_FLAGS_INPUT_DEVICES) != 0, + BZ_IMPORTANCE_INFORMATION, + "input-keyboard-symbolic", + _ ("Input Device Access"), + _ ("Can access input devices"), + NULL, NULL, NULL); + add_row_if_permission (store, + (perm_flags & BZ_APP_PERMISSIONS_FLAGS_AUDIO_DEVICES) != 0, + BZ_IMPORTANCE_INFORMATION, + "permissions-microphone-symbolic", + _ ("Microphone Access and Audio Playback"), + _ ("Can listen using microphones and play audio without asking permission"), + NULL, NULL, NULL); + add_row_if_permission (store, + (perm_flags & BZ_APP_PERMISSIONS_FLAGS_SYSTEM_DEVICES) != 0, + BZ_IMPORTANCE_WARNING, + "permissions-system-devices-symbolic", + _ ("System Device Access"), + _ ("Can access system devices which require elevated permissions"), + NULL, NULL, NULL); + add_row_if_permission (store, + (perm_flags & BZ_APP_PERMISSIONS_FLAGS_SCREEN) != 0, + BZ_IMPORTANCE_WARNING, + "permissions-screen-contents-symbolic", + _ ("Screen Contents Access"), + _ ("Can access the contents of the screen or other windows"), + NULL, NULL, NULL); + add_row_if_permission (store, + (perm_flags & BZ_APP_PERMISSIONS_FLAGS_X11) != 0, + BZ_IMPORTANCE_IMPORTANT, + "permissions-legacy-windowing-system-symbolic", + _ ("Legacy Windowing System"), + _ ("Always uses a legacy windowing system (X11)"), + NULL, NULL, NULL); + add_row_if_permission (store, + (perm_flags & BZ_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX) != 0, + BZ_IMPORTANCE_IMPORTANT, + "earth-symbolic", + _ ("Arbitrary Permissions"), + _ ("Can acquire arbitrary permissions"), + NULL, NULL, NULL); + add_row_if_permission (store, + (perm_flags & BZ_APP_PERMISSIONS_FLAGS_SETTINGS) != 0, + BZ_IMPORTANCE_WARNING, + "emblem-system-symbolic", + _ ("User Settings"), + _ ("Can access and change user settings"), + NULL, NULL, NULL); + add_row_if_permission (store, + (perm_flags & BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL) != 0, + BZ_IMPORTANCE_IMPORTANT, + "drive-harddisk-symbolic", + _ ("Full File System Read/Write Access"), + _ ("Can read and write all data on the file system"), + NULL, NULL, NULL); + add_row_if_permission (store, + ((perm_flags & BZ_APP_PERMISSIONS_FLAGS_HOME_FULL) != 0 && + !(perm_flags & BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL)), + BZ_IMPORTANCE_IMPORTANT, + "user-home-symbolic", + _ ("Home Folder Read/Write Access"), + _ ("Can read and write all data in your home directory"), + NULL, NULL, NULL); + add_row_if_permission (store, + ((perm_flags & BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ) != 0 && + !(perm_flags & BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL)), + BZ_IMPORTANCE_IMPORTANT, + "folder-symbolic", + _ ("Full File System Read Access"), + _ ("Can read all data on the file system"), + NULL, NULL, NULL); + add_row_if_permission (store, + ((perm_flags & BZ_APP_PERMISSIONS_FLAGS_HOME_READ) != 0 && + !(perm_flags & (BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL | + BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ))), + BZ_IMPORTANCE_IMPORTANT, + "user-home-symbolic", + _ ("Home Folder Read Access"), + _ ("Can read all data in your home directory"), + NULL, NULL, NULL); + add_row_if_permission (store, + ((perm_flags & BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_FULL) != 0 && + !(perm_flags & (BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL | + BZ_APP_PERMISSIONS_FLAGS_HOME_FULL))), + fs_importance, + "folder-download-symbolic", + _ ("Download Folder Read/Write Access"), + _ ("Can read and write all data in your downloads directory"), + NULL, NULL, NULL); + add_row_if_permission (store, + ((perm_flags & BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_READ) != 0 && + !(perm_flags & (BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL | + BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ | + BZ_APP_PERMISSIONS_FLAGS_HOME_FULL | + BZ_APP_PERMISSIONS_FLAGS_HOME_READ))), + fs_importance, + "folder-download-symbolic", + _ ("Download Folder Read Access"), + _ ("Can read all data in your downloads directory"), + NULL, NULL, NULL); + + for (i = 0; filesystem_full != NULL && i < filesystem_full->len; i++) { - filesystem_read = bz_app_permissions_get_filesystem_read (permissions); - filesystem_full = bz_app_permissions_get_filesystem_full (permissions); - bus_policies = bz_app_permissions_get_bus_policies (permissions, &n_bus_policies); + const BzFilesystemPath *path = g_ptr_array_index (filesystem_full, i); + g_autofree char *fs_title = bz_filesystem_path_to_display_string (path); + const char *fs_icon = bz_filesystem_path_to_icon_name (path); - add_row_if_permission (store, - bz_app_permissions_is_empty (permissions), - BZ_IMPORTANCE_UNIMPORTANT, - "permissions-sandboxed-symbolic", - _ ("No Permissions"), - _ ("App is fully sandboxed"), - NULL, NULL, NULL); - add_row_if_permission (store, - (perm_flags & BZ_APP_PERMISSIONS_FLAGS_NETWORK) != 0, - BZ_IMPORTANCE_INFORMATION, - "network-wireless-symbolic", - _ ("Network Access"), - _ ("Can access the internet"), - "network-wireless-disabled-symbolic", - _ ("No Network Access"), - _ ("Cannot access the internet")); - add_row_if_permission (store, - (perm_flags & BZ_APP_PERMISSIONS_FLAGS_DEVICES) != 0, - (perm_flags & BZ_APP_PERMISSIONS_FLAGS_NETWORK) ? BZ_IMPORTANCE_WARNING : BZ_IMPORTANCE_INFORMATION, - "camera-photo-symbolic", - _ ("User Device Access"), - _ ("Can access devices such as webcams or gaming controllers"), - "camera-disabled-symbolic", - _ ("No User Device Access"), - _ ("Cannot access devices such as webcams or gaming controllers")); - add_row_if_permission (store, - (perm_flags & BZ_APP_PERMISSIONS_FLAGS_INPUT_DEVICES) != 0, - BZ_IMPORTANCE_INFORMATION, - "input-keyboard-symbolic", - _ ("Input Device Access"), - _ ("Can access input devices"), - NULL, NULL, NULL); - add_row_if_permission (store, - (perm_flags & BZ_APP_PERMISSIONS_FLAGS_AUDIO_DEVICES) != 0, - BZ_IMPORTANCE_INFORMATION, - "permissions-microphone-symbolic", - _ ("Microphone Access and Audio Playback"), - _ ("Can listen using microphones and play audio without asking permission"), - NULL, NULL, NULL); - add_row_if_permission (store, - (perm_flags & BZ_APP_PERMISSIONS_FLAGS_SYSTEM_DEVICES) != 0, - BZ_IMPORTANCE_WARNING, - "permissions-system-devices-symbolic", - _ ("System Device Access"), - _ ("Can access system devices which require elevated permissions"), - NULL, NULL, NULL); - add_row_if_permission (store, - (perm_flags & BZ_APP_PERMISSIONS_FLAGS_SCREEN) != 0, - BZ_IMPORTANCE_WARNING, - "permissions-screen-contents-symbolic", - _ ("Screen Contents Access"), - _ ("Can access the contents of the screen or other windows"), - NULL, NULL, NULL); - add_row_if_permission (store, - (perm_flags & BZ_APP_PERMISSIONS_FLAGS_X11) != 0, - BZ_IMPORTANCE_IMPORTANT, - "permissions-legacy-windowing-system-symbolic", - _ ("Legacy Windowing System"), - _ ("Always uses a legacy windowing system (X11)"), - NULL, NULL, NULL); - add_row_if_permission (store, - (perm_flags & BZ_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX) != 0, - BZ_IMPORTANCE_IMPORTANT, - "earth-symbolic", - _ ("Arbitrary Permissions"), - _ ("Can acquire arbitrary permissions"), - NULL, NULL, NULL); - add_row_if_permission (store, - (perm_flags & BZ_APP_PERMISSIONS_FLAGS_SETTINGS) != 0, - BZ_IMPORTANCE_WARNING, - "emblem-system-symbolic", - _ ("User Settings"), - _ ("Can access and change user settings"), - NULL, NULL, NULL); - add_row_if_permission (store, - (perm_flags & BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL) != 0, - BZ_IMPORTANCE_IMPORTANT, - "drive-harddisk-symbolic", - _ ("Full File System Read/Write Access"), - _ ("Can read and write all data on the file system"), - NULL, NULL, NULL); - add_row_if_permission (store, - ((perm_flags & BZ_APP_PERMISSIONS_FLAGS_HOME_FULL) != 0 && - !(perm_flags & BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL)), - BZ_IMPORTANCE_IMPORTANT, - "user-home-symbolic", - _ ("Home Folder Read/Write Access"), - _ ("Can read and write all data in your home directory"), - NULL, NULL, NULL); - add_row_if_permission (store, - ((perm_flags & BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ) != 0 && - !(perm_flags & BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL)), - BZ_IMPORTANCE_IMPORTANT, - "folder-symbolic", - _ ("Full File System Read Access"), - _ ("Can read all data on the file system"), - NULL, NULL, NULL); - add_row_if_permission (store, - ((perm_flags & BZ_APP_PERMISSIONS_FLAGS_HOME_READ) != 0 && - !(perm_flags & (BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL | - BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ))), - BZ_IMPORTANCE_IMPORTANT, - "user-home-symbolic", - _ ("Home Folder Read Access"), - _ ("Can read all data in your home directory"), - NULL, NULL, NULL); - add_row_if_permission (store, - ((perm_flags & BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_FULL) != 0 && - !(perm_flags & (BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL | - BZ_APP_PERMISSIONS_FLAGS_HOME_FULL))), - fs_importance, - "folder-download-symbolic", - _ ("Download Folder Read/Write Access"), - _ ("Can read and write all data in your downloads directory"), - NULL, NULL, NULL); - add_row_if_permission (store, - ((perm_flags & BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_READ) != 0 && - !(perm_flags & (BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL | - BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ | - BZ_APP_PERMISSIONS_FLAGS_HOME_FULL | - BZ_APP_PERMISSIONS_FLAGS_HOME_READ))), - fs_importance, - "folder-download-symbolic", - _ ("Download Folder Read Access"), - _ ("Can read all data in your downloads directory"), - NULL, NULL, NULL); + if (perm_flags & BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL) + continue; - for (i = 0; filesystem_full != NULL && i < filesystem_full->len; i++) + if ((perm_flags & BZ_APP_PERMISSIONS_FLAGS_HOME_FULL) && + path->type == BZ_FILESYSTEM_PATH_HOME_SUBDIR) + continue; + + add_row_if_permission (store, TRUE, fs_importance, fs_icon, fs_title, + _ ("Can read and write all data in the directory"), + NULL, NULL, NULL); + } + + for (i = 0; filesystem_read != NULL && i < filesystem_read->len; i++) + { + const BzFilesystemPath *path = g_ptr_array_index (filesystem_read, i); + g_autofree char *fs_title = bz_filesystem_path_to_display_string (path); + const char *fs_icon = bz_filesystem_path_to_icon_name (path); + + if (perm_flags & BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL) + continue; + + if ((perm_flags & (BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ | + BZ_APP_PERMISSIONS_FLAGS_HOME_FULL | + BZ_APP_PERMISSIONS_FLAGS_HOME_READ)) && + path->type == BZ_FILESYSTEM_PATH_HOME_SUBDIR) + continue; + + add_row_if_permission (store, TRUE, fs_importance, fs_icon, fs_title, + _ ("Can read all data in the directory"), + NULL, NULL, NULL); + } + + if (include_no_access_rows) + add_row_if_permission (store, + !(perm_flags & (BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL | + BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ | + BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_OTHER | + BZ_APP_PERMISSIONS_FLAGS_HOME_FULL | + BZ_APP_PERMISSIONS_FLAGS_HOME_READ | + BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_FULL | + BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_READ)) && + filesystem_read == NULL && filesystem_full == NULL, + BZ_IMPORTANCE_UNIMPORTANT, + "folder-symbolic", + _ ("No File System Access"), + _ ("Cannot access the file system at all"), + NULL, NULL, NULL); + + add_row_if_permission (store, + (perm_flags & BZ_APP_PERMISSIONS_FLAGS_SYSTEM_BUS) != 0, + BZ_IMPORTANCE_WARNING, + "emblem-system-symbolic", + _ ("Uses System Services"), + _ ("Can request data from non-portal system services"), + NULL, NULL, NULL); + add_row_if_permission (store, + (perm_flags & BZ_APP_PERMISSIONS_FLAGS_SESSION_BUS) != 0, + BZ_IMPORTANCE_WARNING, + "emblem-system-symbolic", + _ ("Uses Session Services"), + _ ("Can request data from non-portal session services"), + NULL, NULL, NULL); + + for (i = 0; i < n_bus_policies; i++) + { + const BzBusPolicy *policy = bus_policies[i]; + const char *well_known_title = NULL; + const char *well_known_desc = NULL; + g_autofree char *bus_title = NULL; + const char *bus_description = NULL; + gboolean is_system_tray = FALSE; + + is_system_tray = g_str_equal (policy->bus_name, "org.kde.StatusNotifierWatcher") || + g_str_equal (policy->bus_name, "com.canonical.indicator.application"); + + if (is_system_tray && has_system_tray) // if not filtered, then there would be 2 entries for tray icon + continue; + + if (is_system_tray) + has_system_tray = TRUE; + + if (lookup_well_known_bus_policy (policy->bus_name, &well_known_title, &well_known_desc)) { - const BzFilesystemPath *path = g_ptr_array_index (filesystem_full, i); - g_autofree char *fs_title = bz_filesystem_path_to_display_string (path); - const char *fs_icon = bz_filesystem_path_to_icon_name (path); - - if (perm_flags & BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL) - continue; - - if ((perm_flags & BZ_APP_PERMISSIONS_FLAGS_HOME_FULL) && - path->type == BZ_FILESYSTEM_PATH_HOME_SUBDIR) - continue; - - add_row_if_permission (store, - TRUE, - fs_importance, - fs_icon, - fs_title, - _ ("Can read and write all data in the directory"), - NULL, NULL, NULL); + bus_title = g_strdup (well_known_title); + bus_description = well_known_desc; + } + else + { + bus_title = format_bus_policy_title (policy); + bus_description = format_bus_policy_subtitle (policy); } - for (i = 0; filesystem_read != NULL && i < filesystem_read->len; i++) - { - const BzFilesystemPath *path = g_ptr_array_index (filesystem_read, i); - g_autofree char *fs_title = bz_filesystem_path_to_display_string (path); - const char *fs_icon = bz_filesystem_path_to_icon_name (path); - - if (perm_flags & BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL) - continue; - - if ((perm_flags & (BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ | - BZ_APP_PERMISSIONS_FLAGS_HOME_FULL | - BZ_APP_PERMISSIONS_FLAGS_HOME_READ)) && - path->type == BZ_FILESYSTEM_PATH_HOME_SUBDIR) - continue; - - add_row_if_permission (store, - TRUE, - fs_importance, - fs_icon, - fs_title, - _ ("Can read all data in the directory"), - NULL, NULL, NULL); - } - - add_row_if_permission (store, - !(perm_flags & (BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL | - BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ | - BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_OTHER | - BZ_APP_PERMISSIONS_FLAGS_HOME_FULL | - BZ_APP_PERMISSIONS_FLAGS_HOME_READ | - BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_FULL | - BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_READ)) && - filesystem_read == NULL && filesystem_full == NULL, - BZ_IMPORTANCE_UNIMPORTANT, - "folder-symbolic", - _ ("No File System Access"), - _ ("Cannot access the file system at all"), + add_row_if_permission (store, TRUE, BZ_IMPORTANCE_WARNING, + "emblem-system-symbolic", bus_title, bus_description, NULL, NULL, NULL); + } - add_row_if_permission (store, - (perm_flags & BZ_APP_PERMISSIONS_FLAGS_SYSTEM_BUS) != 0, - BZ_IMPORTANCE_WARNING, - "emblem-system-symbolic", - _ ("Uses System Services"), - _ ("Can request data from non-portal system services"), - NULL, NULL, NULL); - add_row_if_permission (store, - (perm_flags & BZ_APP_PERMISSIONS_FLAGS_SESSION_BUS) != 0, - BZ_IMPORTANCE_WARNING, - "emblem-system-symbolic", - _ ("Uses Session Services"), - _ ("Can request data from non-portal session services"), - NULL, NULL, NULL); + if (include_no_access_rows) + add_row_if_permission (store, + !(perm_flags & (BZ_APP_PERMISSIONS_FLAGS_SYSTEM_BUS | + BZ_APP_PERMISSIONS_FLAGS_SESSION_BUS | + BZ_APP_PERMISSIONS_FLAGS_BUS_POLICY_OTHER)) && + n_bus_policies == 0, + BZ_IMPORTANCE_UNIMPORTANT, + "emblem-system-symbolic", + _ ("No Service Access"), + _ ("Cannot access non-portal session or system services at all"), + NULL, NULL, NULL); - for (i = 0; i < n_bus_policies; i++) - { - const BzBusPolicy *policy = bus_policies[i]; - const char *well_known_title = NULL; - const char *well_known_desc = NULL; - g_autofree char *bus_title = NULL; - const char *bus_description = NULL; - gboolean is_system_tray = FALSE; + return G_LIST_MODEL (store); +} - is_system_tray = g_str_equal (policy->bus_name, "org.kde.StatusNotifierWatcher") || - g_str_equal (policy->bus_name, "com.canonical.indicator.application"); +GListModel * +bz_safety_calculator_analyze_entry (BzEntry *entry) +{ + g_autoptr (BzAppPermissions) permissions = NULL; + g_autoptr (GListModel) permissions_model = NULL; + GListStore *store = NULL; + gboolean is_verified = FALSE; + gboolean is_foss = FALSE; + guint n_items = 0; + guint i = 0; - if (is_system_tray && has_system_tray) // if not filtered, then there would be 2 entries for tray icon - continue; + g_return_val_if_fail (BZ_IS_ENTRY (entry), NULL); - if (is_system_tray) - has_system_tray = TRUE; + is_verified = bz_entry_is_verified (entry); + is_foss = bz_entry_get_is_foss (entry); - if (lookup_well_known_bus_policy (policy->bus_name, &well_known_title, &well_known_desc)) - { - bus_title = g_strdup (well_known_title); - bus_description = well_known_desc; - } - else - { - bus_title = format_bus_policy_title (policy); - bus_description = format_bus_policy_subtitle (policy); - } + g_object_get (entry, "permissions", &permissions, NULL); + permissions_model = bz_safety_calculator_analyze_permissions (permissions, TRUE); - add_row_if_permission (store, - TRUE, - BZ_IMPORTANCE_WARNING, - "emblem-system-symbolic", - bus_title, - bus_description, - NULL, NULL, NULL); - } + store = g_list_store_new (BZ_TYPE_SAFETY_ROW); + n_items = g_list_model_get_n_items (permissions_model); + for (i = 0; i < n_items; i++) + { + g_autoptr (GObject) row = NULL; - add_row_if_permission (store, - !(perm_flags & (BZ_APP_PERMISSIONS_FLAGS_SYSTEM_BUS | - BZ_APP_PERMISSIONS_FLAGS_SESSION_BUS | - BZ_APP_PERMISSIONS_FLAGS_BUS_POLICY_OTHER)) && - n_bus_policies == 0, - BZ_IMPORTANCE_UNIMPORTANT, - "emblem-system-symbolic", - _ ("No Service Access"), - _ ("Cannot access non-portal session or system services at all"), - NULL, NULL, NULL); + row = g_list_model_get_item (permissions_model, i); + g_list_store_append (store, row); } add_row_if_permission (store, @@ -376,8 +394,6 @@ bz_safety_calculator_analyze_entry (BzEntry *entry) _ ("The source code is public and can be independently audited, which makes the app more likely to be safe")); } - g_clear_object (&permissions); - return G_LIST_MODEL (store); } diff --git a/src/safety-calculator.h b/src/safety-calculator.h index f07510a..03aba5d 100644 --- a/src/safety-calculator.h +++ b/src/safety-calculator.h @@ -20,6 +20,7 @@ #pragma once +#include "bz-app-permissions.h" #include "bz-context-row.h" #include "bz-entry.h" #include "bz-safety-row.h" @@ -36,6 +37,10 @@ typedef enum #define BZ_TYPE_HIGH_RISK_GROUP (bz_high_risk_group_get_type ()) +GListModel * +bz_safety_calculator_analyze_permissions (BzAppPermissions *permissions, + gboolean include_no_access_rows); + GListModel * bz_safety_calculator_analyze_entry (BzEntry *entry);