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.
This commit is contained in:
Alexander Vanhee 2026-09-18 14:05:00 +02:00
parent a035eec69c
commit c32c29d97d
18 changed files with 1167 additions and 901 deletions

View file

@ -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

View file

@ -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)

View file

@ -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);

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -19,6 +19,7 @@
*/
#include <glib/gi18n.h>
#include <bzvala.h>
#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"

View file

@ -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 <https://www.gnu.org/licenses/>.
*
* 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;
}
}

11
src/bz-update-info.txt Normal file
View file

@ -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

View file

@ -33,8 +33,3 @@ template $BzUpdatesCard: Adw.Bin {
}
};
}
FilterListModel runtimes_filter_model {
filter: CustomFilter runtimes_filter {};
model: bind template.state as <$BzStateInfo>.available-updates;
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "config.h"
#include <glib/gi18n.h>
#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);
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include "bz-state-info.h"
#include <adwaita.h>
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

213
src/bz-updates-card.vala Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*
* 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<Gtk.Widget> app_rows = new GenericArray<Gtk.Widget> ();
private Gtk.Widget? runtimes_row = null;
private GenericArray<Bz.Entry> runtime_entries = new GenericArray<Bz.Entry> ();
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<Bz.Entry> ();
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<Bz.Entry> 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<Gtk.Widget> ();
runtime_entries = new GenericArray<Bz.Entry> ();
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<Bz.Entry> ();
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;
}
}

View file

@ -21,8 +21,8 @@
// This file is an utter mess
#include "config.h"
#include <glib/gi18n.h>
#include <bzvala.h>
#include <glib/gi18n.h>
#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')

View file

@ -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 ()")]

View file

@ -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 = [

View file

@ -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);
}

View file

@ -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);