1
0
Fork 0
gnome-control-center/panels/applications/cc-applications-panel.c
Daniel Baumann 0a49575b51
Adding upstream version 1:48.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 19:52:18 +02:00

2011 lines
67 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

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

/* cc-applications-panel.c
*
* Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "cc-applications-panel"
#include <config.h>
#include <glib/gi18n.h>
#ifdef HAVE_MALCONTENT
#include <libmalcontent/malcontent.h>
#endif
#include <gio/gdesktopappinfo.h>
#include "cc-application-shortcut-dialog.h"
#include "cc-applications-panel.h"
#include "cc-applications-row.h"
#include "cc-list-row-info-button.h"
#include "cc-list-row.h"
#include "cc-default-apps-page.h"
#include "cc-removable-media-settings.h"
#include "cc-applications-resources.h"
#include "cc-applications-row.h"
#include "cc-default-apps-page.h"
#include "cc-removable-media-settings.h"
#ifdef HAVE_SNAP
#include "cc-snapd-client.h"
#include "cc-snap-row.h"
#endif
#include "cc-util.h"
#include "globs.h"
#include "search.h"
#include "utils.h"
#define MASTER_SCHEMA "org.gnome.desktop.notifications"
#define APP_SCHEMA MASTER_SCHEMA ".application"
#define APP_PREFIX "/org/gnome/desktop/notifications/application/"
#define GLOBAL_SHORTCUTS_APP_SCHEMA "org.gnome.settings-daemon.global-shortcuts.application"
#define GLOBAL_SHORTCUTS_PATH "/org/gnome/settings-daemon/global-shortcuts/"
#define PORTAL_SNAP_PREFIX "snap."
struct _CcApplicationsPanel
{
CcPanel parent;
CcDefaultAppsPage *default_apps_page;
AdwSwitchRow *autorun_never_row;
CcRemovableMediaSettings *removable_media_settings;
AdwNavigationPage *app_settings_page;
GtkStack *main_page_stack;
GtkListBox *app_listbox;
GtkEntry *app_search_entry;
GAppInfoMonitor *monitor;
gulong monitor_id;
GListModel *app_model;
GListModel *filter_model;
GtkFilter *filter;
#ifdef HAVE_MALCONTENT
GCancellable *cancellable;
MctAppFilter *app_filter;
MctManager *manager;
guint app_filter_id;
#endif
gchar *current_app_id;
GAppInfo *current_app_info;
gchar *current_portal_app_id;
GHashTable *globs;
GHashTable *search_providers;
GtkImage *app_icon_image;
GtkLabel *app_name_label;
GtkButton *launch_button;
GtkButton *view_details_button;
AdwBanner *sandbox_banner;
GtkWidget *sandbox_info_button;
GDBusProxy *perm_store;
GSettings *media_handling_settings;
GtkListBoxRow *perm_store_pending_row;
GSettings *notification_settings;
GSettings *location_settings;
GSettings *privacy_settings;
GSettings *search_settings;
GSettings *global_shortcuts_app_settings;
GtkButton *install_button;
AdwPreferencesGroup *permissions_group;
AdwSwitchRow *notifications_row;
AdwSwitchRow *background_row;
AdwSwitchRow *wallpaper_row;
AdwSwitchRow *screenshots_row;
AdwSwitchRow *sounds_row;
CcListRow *no_sounds_row;
AdwSwitchRow *search_row;
CcListRow *no_search_row;
AdwSwitchRow *camera_row;
CcListRow *no_camera_row;
AdwSwitchRow *location_row;
CcListRow *no_location_row;
AdwSwitchRow *shortcuts_row;
AdwSwitchRow *microphone_row;
CcListRow *no_microphone_row;
CcListRow *global_shortcuts_row;
AdwPreferencesGroup *required_permissions_group;
CcListRow *builtin_row;
AdwPreferencesPage *builtin_page;
GtkListBox *builtin_list;
GList *snap_permission_rows;
AdwButtonRow *handler_reset_button_row;
AdwPreferencesPage *handler_page;
CcListRow *handler_row;
AdwPreferencesGroup *handler_file_group;
AdwPreferencesGroup *handler_link_group;
GList *file_handler_rows;
GList *link_handler_rows;
GtkWidget *general_group;
CcListRow *storage_row;
AdwPreferencesPage *storage_page;
AdwActionRow *storage_page_app_row;
AdwActionRow *storage_page_data_row;
AdwActionRow *storage_page_cache_row;
AdwActionRow *storage_page_total_row;
AdwButtonRow *clear_cache_button_row;
guint64 app_size;
guint64 cache_size;
guint64 data_size;
};
CC_PANEL_REGISTER (CcApplicationsPanel, cc_applications_panel)
static void select_app (CcApplicationsPanel *self,
const gchar *app_id,
gboolean emit_activate);
static void update_handler_dialog (CcApplicationsPanel *self, GAppInfo *info);
static void update_usage_section (CcApplicationsPanel *self, GAppInfo *info);
enum
{
PROP_0,
PROP_PARAMETERS
};
static gboolean
gnome_software_is_installed (void)
{
g_autofree gchar *path = g_find_program_in_path ("gnome-software");
return path != NULL;
}
/* Callbacks */
static gboolean
privacy_link_cb (CcApplicationsPanel *self)
{
CcShell *shell = cc_panel_get_shell (CC_PANEL (self));
g_autoptr(GError) error = NULL;
if (!cc_shell_set_active_panel_from_id (shell, "location", NULL, &error))
g_warning ("Failed to switch to privacy panel: %s", error->message);
return TRUE;
}
static void
open_software_cb (CcApplicationsPanel *self)
{
const gchar *argv[] = { "gnome-software", "--details", "appid", NULL };
g_autofree gchar *argv_app_id = NULL;
if (self->current_app_id == NULL)
argv[1] = NULL;
else if (g_str_has_prefix (self->current_app_id, "org.gnome.Epiphany.WebApp_"))
/* GNOME Software only shows info on the webapp desktop file itself */
argv_app_id = g_strdup_printf ("%s.desktop", self->current_app_id);
else
argv_app_id = g_strdup (self->current_app_id);
argv[2] = argv_app_id;
g_spawn_async (NULL, (char **)argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
}
/* --- portal permissions and utilities --- */
static gchar **
get_portal_permissions (CcApplicationsPanel *self,
const gchar *table,
const gchar *id,
const gchar *app_id)
{
g_autoptr(GVariant) ret = NULL;
g_autoptr(GVariantIter) iter = NULL;
const gchar *key = NULL;
GStrv val;
GStrv result = NULL;
ret = g_dbus_proxy_call_sync (self->perm_store,
"Lookup",
g_variant_new ("(ss)", table, id),
0, G_MAXINT, NULL, NULL);
if (ret == NULL)
return NULL;
g_variant_get (ret, "(a{sas}v)", &iter, NULL);
while (g_variant_iter_loop (iter, "{&s^a&s}", &key, &val))
{
if (strcmp (key, app_id) == 0 && result == NULL)
result = g_strdupv (val);
}
return result;
}
static void
set_portal_permissions (CcApplicationsPanel *self,
const gchar *table,
const gchar *id,
const gchar *app_id,
const gchar * const *permissions)
{
g_autoptr(GError) error = NULL;
g_dbus_proxy_call_sync (self->perm_store,
"SetPermission",
g_variant_new ("(sbss^as)", table, TRUE, id, app_id, permissions),
0,
G_MAXINT,
NULL,
&error);
if (error)
g_warning ("Error setting portal permissions: %s", error->message);
}
static gchar *
get_portal_app_id (GAppInfo *info)
{
if (G_IS_DESKTOP_APP_INFO (info))
{
g_autofree gchar *snap_name = NULL;
gchar *flatpak_id;
flatpak_id = g_desktop_app_info_get_string (G_DESKTOP_APP_INFO (info), "X-Flatpak");
if (flatpak_id != NULL)
return flatpak_id;
snap_name = g_desktop_app_info_get_string (G_DESKTOP_APP_INFO (info), "X-SnapInstanceName");
if (snap_name != NULL)
return g_strdup_printf ("%s%s", PORTAL_SNAP_PREFIX, snap_name);
}
return NULL;
}
static GFile *
get_flatpak_app_dir (const gchar *app_id,
const gchar *subdir)
{
g_autofree gchar *path = NULL;
g_autoptr(GFile) appdir = NULL;
path = g_build_filename (g_get_home_dir (), ".var", "app", app_id, NULL);
appdir = g_file_new_for_path (path);
return g_file_get_child (appdir, subdir);
}
/* --- search settings --- */
static void
set_search_enabled (CcApplicationsPanel *self,
const gchar *app_id,
gboolean enabled)
{
g_autoptr(GPtrArray) new_apps = NULL;
g_autofree gchar *desktop_id = NULL;
g_auto(GStrv) apps = NULL;
gpointer key, value;
gboolean default_disabled;
gint i;
desktop_id = g_strconcat (app_id, ".desktop", NULL);
if (!g_hash_table_lookup_extended (self->search_providers, app_id, &key, &value))
{
g_warning ("Trying to configure search for a provider-less app - this shouldn't happen");
return;
}
default_disabled = GPOINTER_TO_INT (value);
new_apps = g_ptr_array_new_with_free_func (g_free);
if (default_disabled)
{
apps = g_settings_get_strv (self->search_settings, "enabled");
for (i = 0; apps[i]; i++)
{
if (strcmp (apps[i], desktop_id) != 0)
g_ptr_array_add (new_apps, g_strdup (apps[i]));
}
if (enabled)
g_ptr_array_add (new_apps, g_strdup (desktop_id));
g_ptr_array_add (new_apps, NULL);
g_settings_set_strv (self->search_settings, "enabled", (const gchar * const *)new_apps->pdata);
}
else
{
apps = g_settings_get_strv (self->search_settings, "disabled");
for (i = 0; apps[i]; i++)
{
if (strcmp (apps[i], desktop_id) != 0)
g_ptr_array_add (new_apps, g_strdup (apps[i]));
}
if (!enabled)
g_ptr_array_add (new_apps, g_strdup (desktop_id));
g_ptr_array_add (new_apps, NULL);
g_settings_set_strv (self->search_settings, "disabled", (const gchar * const *)new_apps->pdata);
}
}
static gboolean
search_contains_string_for_app (CcApplicationsPanel *self,
const gchar *app_id,
const gchar *setting)
{
g_autofree gchar *desktop_id = NULL;
g_auto(GStrv) apps = NULL;
desktop_id = g_strconcat (app_id, ".desktop", NULL);
apps = g_settings_get_strv (self->search_settings, setting);
return g_strv_contains ((const gchar * const *)apps, desktop_id);
}
static gboolean
search_enabled_for_app (CcApplicationsPanel *self,
const gchar *app_id)
{
return search_contains_string_for_app (self, app_id, "enabled");
}
static gboolean
search_disabled_for_app (CcApplicationsPanel *self,
const gchar *app_id)
{
return search_contains_string_for_app (self, app_id, "disabled");
}
static void
get_search_enabled (CcApplicationsPanel *self,
const gchar *app_id,
gboolean *set,
gboolean *enabled)
{
gpointer key, value;
*enabled = FALSE;
*set = g_hash_table_lookup_extended (self->search_providers, app_id, &key, &value);
if (!*set)
return;
if (search_enabled_for_app (self, app_id))
*enabled = TRUE;
else if (search_disabled_for_app (self, app_id))
*enabled = FALSE;
else
*enabled = !GPOINTER_TO_INT (value);
}
static void
search_cb (CcApplicationsPanel *self)
{
if (self->current_app_id)
set_search_enabled (self,
self->current_app_id,
adw_switch_row_get_active (self->search_row));
}
/* --- notification permissions (flatpaks and non-flatpak) --- */
static void
get_notification_allowed (CcApplicationsPanel *self,
const gchar *app_id,
gboolean *set,
gboolean *allowed)
{
if (self->notification_settings)
{
/* FIXME */
*set = TRUE;
*allowed = g_settings_get_boolean (self->notification_settings, "enable");
}
else
{
g_auto(GStrv) perms = get_portal_permissions (self, "notifications", "notification", app_id);
*set = perms != NULL;
/* FIXME: needs unreleased xdg-desktop-portals to write permissions on use */
*set = TRUE;
*allowed = perms == NULL || g_strcmp0 (perms[0], "no") != 0;
}
}
static void
set_notification_allowed (CcApplicationsPanel *self,
gboolean allowed)
{
if (self->notification_settings)
{
g_settings_set_boolean (self->notification_settings, "enable", allowed);
}
else
{
const gchar *perms[2] = { NULL, NULL };
perms[0] = allowed ? "yes" : "no";
set_portal_permissions (self, "notifications", "notification", self->current_portal_app_id, perms);
}
}
static void
notification_cb (CcApplicationsPanel *self)
{
if (self->current_app_id)
set_notification_allowed (self, adw_switch_row_get_active (self->notifications_row));
}
static gchar *
munge_app_id (const gchar *app_id)
{
gchar *id = g_strdup (app_id);
gint i;
g_strcanon (id,
"0123456789"
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"-",
'-');
for (i = 0; id[i] != '\0'; i++)
id[i] = g_ascii_tolower (id[i]);
return id;
}
static GSettings *
get_notification_settings (const gchar *app_id)
{
g_autofree gchar *munged_app_id = munge_app_id (app_id);
g_autofree gchar *path = g_strconcat (APP_PREFIX, munged_app_id, "/", NULL);
return g_settings_new_with_path (APP_SCHEMA, path);
}
/* --- background --- */
static void
get_background_allowed (CcApplicationsPanel *self,
const gchar *app_id,
gboolean *set,
gboolean *allowed)
{
g_auto(GStrv) perms = get_portal_permissions (self, "background", "background", app_id);
*set = TRUE;
*allowed = perms == NULL || g_strcmp0 (perms[0], "no") != 0;
}
static void
set_background_allowed (CcApplicationsPanel *self,
gboolean allowed)
{
const gchar *perms[2] = { NULL, NULL };
perms[0] = allowed ? "yes" : "no";
set_portal_permissions (self, "background", "background", self->current_portal_app_id, perms);
}
static void
background_cb (CcApplicationsPanel *self)
{
if (self->current_app_id)
set_background_allowed (self, adw_switch_row_get_active (self->background_row));
}
/* --- wallpaper --- */
static void
get_wallpaper_allowed (CcApplicationsPanel *self,
const gchar *app_id,
gboolean *set,
gboolean *allowed)
{
g_auto(GStrv) perms = get_portal_permissions (self, "wallpaper", "wallpaper", app_id);
*set = perms != NULL;
*allowed = perms == NULL || g_strcmp0 (perms[0], "no") != 0;
}
static void
set_wallpaper_allowed (CcApplicationsPanel *self,
gboolean allowed)
{
const gchar *perms[2] = { NULL, NULL };
perms[0] = allowed ? "yes" : "no";
set_portal_permissions (self, "wallpaper", "wallpaper", self->current_app_id, perms);
}
static void
wallpaper_cb (CcApplicationsPanel *self)
{
if (self->current_app_id)
set_wallpaper_allowed (self, adw_switch_row_get_active (self->wallpaper_row));
}
/* --- screenshot --- */
static void
get_screenshot_allowed (CcApplicationsPanel *self,
const gchar *app_id,
gboolean *set,
gboolean *allowed)
{
g_auto(GStrv) perms = get_portal_permissions (self, "screenshot", "screenshot", app_id);
*set = perms != NULL;
*allowed = perms == NULL || g_strcmp0 (perms[0], "no") != 0;
}
static void
set_screenshot_allowed (CcApplicationsPanel *self,
gboolean allowed)
{
const gchar *perms[2] = { NULL, NULL };
perms[0] = allowed ? "yes" : "no";
set_portal_permissions (self, "screenshot", "screenshot", self->current_app_id, perms);
}
static void
screenshot_cb (CcApplicationsPanel *self)
{
if (self->current_app_id)
set_screenshot_allowed (self, adw_switch_row_get_active (self->screenshots_row));
}
/* --- shortcuts permissions (flatpak) --- */
static void
get_shortcuts_allowed (CcApplicationsPanel *self,
const gchar *app_id,
gboolean *set,
gboolean *granted)
{
g_auto(GStrv) perms = NULL;
perms = get_portal_permissions (self, "gnome", "shortcuts-inhibitor", app_id);
/* GNOME Shell's "inhibit shortcut dialog" sets the permission to "GRANTED" if
* the user allowed for the keyboard shortcuts to be inhibited, check for that
* string value here.
*/
*set = perms != NULL;
*granted = (perms != NULL) && (perms[0] != NULL) && g_ascii_strcasecmp (perms[0], "GRANTED") == 0;
}
static void
set_shortcuts_allowed (CcApplicationsPanel *self,
gboolean granted)
{
const gchar *perms[2];
g_autofree gchar *desktop_id = g_strconcat (self->current_app_id, ".desktop", NULL);
/* "GRANTED" and "DENIED" here match the values set by the "inhibit shortcut
* dialog" is GNOME Shell:
* https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/inhibitShortcutsDialog.js
*/
perms[0] = granted ? "GRANTED" : "DENIED";
perms[1] = NULL;
set_portal_permissions (self, "gnome", "shortcuts-inhibitor", desktop_id, perms);
}
static void
shortcuts_cb (CcApplicationsPanel *self)
{
if (self->current_app_id)
set_shortcuts_allowed (self, adw_switch_row_get_active (self->shortcuts_row));
}
/* --- device (microphone, camera, speaker) permissions (flatpak) --- */
static void
get_device_allowed (CcApplicationsPanel *self,
const gchar *device,
const gchar *app_id,
gboolean *set,
gboolean *allowed)
{
g_auto(GStrv) perms = NULL;
perms = get_portal_permissions (self, "devices", device, app_id);
*set = perms != NULL;
*allowed = perms == NULL || g_strcmp0 (perms[0], "no") != 0;
}
static void
set_device_allowed (CcApplicationsPanel *self,
const gchar *device,
gboolean allowed)
{
const gchar *perms[2];
perms[0] = allowed ? "yes" : "no";
perms[1] = NULL;
set_portal_permissions (self, "devices", device, self->current_portal_app_id, perms);
}
static void
microphone_cb (CcApplicationsPanel *self)
{
if (self->current_portal_app_id)
set_device_allowed (self, "microphone", adw_switch_row_get_active (self->microphone_row));
}
static void
sound_cb (CcApplicationsPanel *self)
{
if (self->current_portal_app_id)
set_device_allowed (self, "speakers", adw_switch_row_get_active (self->sounds_row));
}
static void
camera_cb (CcApplicationsPanel *self)
{
if (self->current_portal_app_id)
set_device_allowed (self, "camera", adw_switch_row_get_active (self->camera_row));
}
/* --- location permissions (flatpak) --- */
static void
get_location_allowed (CcApplicationsPanel *self,
const gchar *app_id,
gboolean *set,
gboolean *allowed)
{
g_auto(GStrv) perms = NULL;
perms = get_portal_permissions (self, "location", "location", app_id);
*set = perms != NULL;
*allowed = perms == NULL || g_strcmp0 (perms[0], "NONE") != 0;
}
static void
set_location_allowed (CcApplicationsPanel *self,
gboolean allowed)
{
const gchar *perms[3];
/* FIXME allow setting accuracy */
perms[0] = allowed ? "EXACT" : "NONE";
perms[1] = "0";
perms[2] = NULL;
set_portal_permissions (self, "location", "location", self->current_portal_app_id, perms);
}
static void
location_cb (CcApplicationsPanel *self)
{
if (self->current_portal_app_id)
set_location_allowed (self, adw_switch_row_get_active (self->location_row));
}
static void
dialog_closed_cb (CcApplicationsPanel *self)
{
update_usage_section (self, self->current_app_info);
}
static void
global_shortcuts_cb (CcApplicationsPanel *self)
{
CcShell *shell = cc_panel_get_shell (CC_PANEL (self));
AdwDialog *shortcut_dialog;
shortcut_dialog = ADW_DIALOG (cc_application_shortcut_dialog_new (self->current_app_id));
adw_dialog_present (shortcut_dialog, cc_shell_get_toplevel (shell));
g_signal_connect_object (shortcut_dialog, "closed",
G_CALLBACK (dialog_closed_cb), self,
G_CONNECT_SWAPPED);
}
/* --- permissions section --- */
#ifdef HAVE_SNAP
static void
remove_snap_permissions (CcApplicationsPanel *self)
{
GList *l;
for (l = self->snap_permission_rows; l; l = l->next)
adw_preferences_group_remove (self->permissions_group, l->data);
g_clear_pointer (&self->snap_permission_rows, g_list_free);
}
static gboolean
add_snap_permissions (CcApplicationsPanel *self,
GAppInfo *info,
const gchar *app_id)
{
const gchar *snap_name;
g_autoptr(CcSnapdClient) client = NULL;
g_autoptr(JsonArray) plugs = NULL;
g_autoptr(JsonArray) slots = NULL;
gint added = 0;
g_autoptr(GError) error = NULL;
g_autoptr(GError) interfaces_error = NULL;
if (!g_str_has_prefix (app_id, PORTAL_SNAP_PREFIX))
return FALSE;
snap_name = app_id + strlen (PORTAL_SNAP_PREFIX);
client = cc_snapd_client_new ();
if (!cc_snapd_client_get_all_connections_sync (client, &plugs, &slots, cc_panel_get_cancellable (CC_PANEL (self)), &error))
{
g_warning ("Failed to get snap connections: %s", error->message);
return FALSE;
}
for (guint i = 0; i < json_array_get_length (plugs); i++)
{
JsonObject *plug = json_array_get_object_element (plugs, i);
const gchar *plug_interface;
CcSnapRow *row;
g_autoptr(JsonArray) available_slots = NULL;
const gchar * const hidden_interfaces[] = { "content",
"desktop", "desktop-legacy",
"mir",
"unity7", "unity8",
"wayland",
"x11",
NULL };
/* Skip if not relating to this snap */
if (g_strcmp0 (json_object_get_string_member (plug, "snap"), snap_name) != 0)
continue;
/* Ignore interfaces that are too low level to make sense to show or disable */
plug_interface = json_object_get_string_member (plug, "interface");
if (g_strv_contains (hidden_interfaces, plug_interface))
continue;
available_slots = json_array_new ();
for (guint j = 0; j < json_array_get_length (slots); j++)
{
JsonObject *slot = json_array_get_object_element (slots, j);
if (g_strcmp0 (plug_interface, json_object_get_string_member (slot, "interface")) != 0)
continue;
json_array_add_object_element (available_slots, slot);
}
row = cc_snap_row_new (cc_panel_get_cancellable (CC_PANEL (self)), plug, available_slots);
adw_preferences_group_add (self->permissions_group, GTK_WIDGET (row));
self->snap_permission_rows = g_list_prepend (self->snap_permission_rows, row);
added++;
}
return added > 0;
}
#endif
static void
update_sandbox_banner (CcApplicationsPanel *self,
const gchar *app_id,
gboolean is_sandboxed)
{
gboolean is_system = FALSE;
gboolean show_banner = FALSE;
static const gchar *system_apps[] = {
"org.gnome.Settings",
"org.gnome.Nautilus",
"org.gnome.DiskUtility",
"org.gnome.Tour",
"org.gnome.baobab",
"yelp",
};
gsize i;
for (i = 0; i < G_N_ELEMENTS (system_apps); i++)
{
is_system = g_str_equal (system_apps[i], app_id);
if (is_system)
break;
}
show_banner = !is_sandboxed && !is_system;
gtk_widget_set_visible (GTK_WIDGET (self->sandbox_banner), show_banner);
}
static gint
add_static_permission_row (CcApplicationsPanel *self,
const gchar *title,
const gchar *subtitle)
{
GtkWidget *row;
row = adw_action_row_new ();
adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), title);
adw_action_row_set_subtitle (ADW_ACTION_ROW (row), subtitle);
gtk_widget_add_css_class (row, "property");
gtk_list_box_append (self->builtin_list, row);
return 1;
}
static gboolean
add_static_permissions (CcApplicationsPanel *self,
GAppInfo *info,
const gchar *portal_app_id)
{
g_autoptr(GKeyFile) keyfile = NULL;
g_auto(GStrv) sockets = NULL;
g_auto(GStrv) devices = NULL;
g_auto(GStrv) shared = NULL;
g_auto(GStrv) filesystems = NULL;
g_autofree gchar *str = NULL;
g_autofree gchar *app_id = NULL;
gint added = 0;
g_autofree gchar *text = NULL;
g_autofree gchar *static_permissions_number = NULL;
gboolean is_sandboxed, is_snap = FALSE;
is_snap = portal_app_id && g_str_has_prefix (portal_app_id, PORTAL_SNAP_PREFIX);
if (portal_app_id && !is_snap)
keyfile = get_flatpak_metadata (portal_app_id);
is_sandboxed = (keyfile != NULL) || is_snap;
app_id = get_app_id (info);
update_sandbox_banner (self, app_id, is_sandboxed);
if (keyfile == NULL)
return FALSE;
sockets = g_key_file_get_string_list (keyfile, "Context", "sockets", NULL, NULL);
if (sockets && g_strv_contains ((const gchar * const*)sockets, "system-bus"))
added += add_static_permission_row (self, _("System Bus"), _("Full access"));
if (sockets && g_strv_contains ((const gchar * const*)sockets, "session-bus"))
added += add_static_permission_row (self, _("Session Bus"), _("Full access"));
devices = g_key_file_get_string_list (keyfile, "Context", "devices", NULL, NULL);
if (devices && g_strv_contains ((const gchar * const*)devices, "all"))
added += add_static_permission_row (self, _("Devices"), _("Full access to /dev"));
shared = g_key_file_get_string_list (keyfile, "Context", "shared", NULL, NULL);
if (shared && g_strv_contains ((const gchar * const*)shared, "network"))
added += add_static_permission_row (self, _("Network"), _("Has network access"));
filesystems = g_key_file_get_string_list (keyfile, "Context", "filesystems", NULL, NULL);
if (filesystems && (g_strv_contains ((const gchar * const *)filesystems, "home") ||
g_strv_contains ((const gchar * const *)filesystems, "home:rw")))
added += add_static_permission_row (self, _("Home"), _("Full access"));
else if (filesystems && g_strv_contains ((const gchar * const *)filesystems, "home:ro"))
added += add_static_permission_row (self, _("Home"), _("Read-only"));
if (filesystems && (g_strv_contains ((const gchar * const *)filesystems, "host") ||
g_strv_contains ((const gchar * const *)filesystems, "host:rw")))
added += add_static_permission_row (self, _("File System"), _("Full access"));
else if (filesystems && g_strv_contains ((const gchar * const *)filesystems, "host:ro"))
added += add_static_permission_row (self, _("File System"), _("Read-only"));
str = g_key_file_get_string (keyfile, "Session Bus Policy", "ca.desrt.dconf", NULL);
if (str && g_str_equal (str, "talk"))
added += add_static_permission_row (self, _("Settings"), _("Can change settings"));
text = g_strdup_printf (_("<b>%s</b> requires access to the following system resources. To stop this access, the app must be removed."), g_app_info_get_display_name (info));
adw_preferences_page_set_description (self->builtin_page, text);
static_permissions_number = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE,
"%u permission",
"%u permissions",
added),
added);
cc_list_row_set_secondary_label (self->builtin_row, static_permissions_number);
return added > 0;
}
static void
remove_static_permissions (CcApplicationsPanel *self)
{
gtk_list_box_remove_all (self->builtin_list);
}
/* --- header section --- */
static void
update_header_section (CcApplicationsPanel *self,
GAppInfo *info)
{
GIcon *icon;
icon = g_app_info_get_icon (info);
gtk_image_set_from_gicon (self->app_icon_image, icon);
gtk_label_set_label (self->app_name_label,
g_app_info_get_display_name (info));
}
/* --- gintegration section --- */
static void
update_permissions_group (CcApplicationsPanel *self,
GAppInfo *info)
{
g_autofree gchar *app_id = get_app_id (info);
g_autofree gchar *portal_app_id = get_portal_app_id (info);
gboolean set, allowed, disabled;
gboolean has_any = FALSE;
disabled = g_settings_get_boolean (self->search_settings, "disable-external");
get_search_enabled (self, app_id, &set, &allowed);
adw_switch_row_set_active (self->search_row, allowed);
gtk_widget_set_visible (GTK_WIDGET (self->search_row), set && !disabled);
gtk_widget_set_visible (GTK_WIDGET (self->no_search_row), set && disabled);
if (app_id != NULL)
{
g_autofree gchar *desktop_id = g_strconcat (app_id, ".desktop", NULL);
get_shortcuts_allowed (self, desktop_id, &set, &allowed);
gtk_widget_set_visible (GTK_WIDGET (self->shortcuts_row), set);
adw_switch_row_set_active (self->shortcuts_row, allowed);
}
else
{
gtk_widget_set_visible (GTK_WIDGET (self->shortcuts_row), FALSE);
}
#ifdef HAVE_SNAP
remove_snap_permissions (self);
#endif
if (portal_app_id != NULL)
{
g_clear_object (&self->notification_settings);
get_notification_allowed (self, portal_app_id, &set, &allowed);
adw_switch_row_set_active (self->notifications_row, allowed);
gtk_widget_set_visible (GTK_WIDGET (self->notifications_row), set);
has_any |= set;
get_background_allowed (self, portal_app_id, &set, &allowed);
adw_switch_row_set_active (self->background_row, allowed);
gtk_widget_set_visible (GTK_WIDGET (self->background_row), set);
has_any |= set;
get_wallpaper_allowed (self, portal_app_id, &set, &allowed);
adw_switch_row_set_active (self->wallpaper_row, allowed);
gtk_widget_set_visible (GTK_WIDGET (self->wallpaper_row), set);
has_any |= set;
get_screenshot_allowed (self, portal_app_id, &set, &allowed);
adw_switch_row_set_active (self->screenshots_row, allowed);
gtk_widget_set_visible (GTK_WIDGET (self->screenshots_row), set);
has_any |= set;
disabled = g_settings_get_boolean (self->privacy_settings, "disable-sound-output");
get_device_allowed (self, "speakers", portal_app_id, &set, &allowed);
adw_switch_row_set_active (self->sounds_row, allowed);
gtk_widget_set_visible (GTK_WIDGET (self->sounds_row), set && !disabled);
gtk_widget_set_visible (GTK_WIDGET (self->no_sounds_row), set && disabled);
disabled = g_settings_get_boolean (self->privacy_settings, "disable-camera");
get_device_allowed (self, "camera", portal_app_id, &set, &allowed);
adw_switch_row_set_active (self->camera_row, allowed);
gtk_widget_set_visible (GTK_WIDGET (self->camera_row), set && !disabled);
gtk_widget_set_visible (GTK_WIDGET (self->no_camera_row), set && disabled);
has_any |= set;
disabled = g_settings_get_boolean (self->privacy_settings, "disable-microphone");
get_device_allowed (self, "microphone", portal_app_id, &set, &allowed);
adw_switch_row_set_active (self->microphone_row, allowed);
gtk_widget_set_visible (GTK_WIDGET (self->microphone_row), set && !disabled);
gtk_widget_set_visible (GTK_WIDGET (self->no_microphone_row), set && disabled);
has_any |= set;
disabled = !g_settings_get_boolean (self->location_settings, "enabled");
get_location_allowed (self, portal_app_id, &set, &allowed);
adw_switch_row_set_active (self->location_row, allowed);
gtk_widget_set_visible (GTK_WIDGET (self->location_row), set && !disabled);
gtk_widget_set_visible (GTK_WIDGET (self->no_location_row), set && disabled);
has_any |= set;
#ifdef HAVE_SNAP
has_any |= add_snap_permissions (self, info, portal_app_id);
#endif
}
else
{
g_set_object (&self->notification_settings, get_notification_settings (app_id));
get_notification_allowed (self, app_id, &set, &allowed);
adw_switch_row_set_active (self->notifications_row, allowed);
gtk_widget_set_visible (GTK_WIDGET (self->notifications_row), set);
has_any |= set;
gtk_widget_set_visible (GTK_WIDGET (self->background_row), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->wallpaper_row), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->screenshots_row), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->sounds_row), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->no_sounds_row), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->camera_row), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->no_camera_row), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->microphone_row), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->no_microphone_row), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->location_row), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->no_location_row), FALSE);
}
gtk_widget_set_visible (GTK_WIDGET (self->permissions_group), has_any);
}
/* --- handler section --- */
static void
unset_cb (CcApplicationsPanel *self,
GtkButton *button)
{
const gchar *type;
type = (const gchar *)g_object_get_data (G_OBJECT (button), "type");
g_app_info_remove_supports_type (self->current_app_info, type, NULL);
update_handler_dialog (self, self->current_app_info);
}
static void
add_scheme (CcApplicationsPanel *self,
const gchar *type)
{
g_autofree gchar *title = NULL;
GtkWidget *button;
GtkWidget *row;
gchar *scheme;
scheme = strrchr (type, '/') + 1;
title = g_strdup_printf ("%s://", scheme);
row = adw_action_row_new ();
adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), title);
button = gtk_button_new_from_icon_name ("edit-delete-symbolic");
gtk_widget_set_tooltip_text (button, _("Remove Link Type"));
gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
gtk_widget_set_halign (button, GTK_ALIGN_END);
gtk_widget_add_css_class (button, "flat");
adw_action_row_add_suffix (ADW_ACTION_ROW (row), button);
g_object_set_data_full (G_OBJECT (button), "type", g_strdup (type), g_free);
g_signal_connect_object (button, "clicked", G_CALLBACK (unset_cb), self, G_CONNECT_SWAPPED);
gtk_widget_set_visible (GTK_WIDGET (self->handler_link_group), TRUE);
adw_preferences_group_add (self->handler_link_group, GTK_WIDGET (row));
self->link_handler_rows = g_list_prepend (self->link_handler_rows, row);
}
static void
add_file_type (CcApplicationsPanel *self,
const gchar *type)
{
g_autofree gchar *desc = NULL;
const gchar *glob;
GtkWidget *button;
GtkWidget *row;
glob = g_hash_table_lookup (self->globs, type);
desc = g_content_type_get_description (type);
row = adw_action_row_new ();
adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), desc);
adw_action_row_set_subtitle (ADW_ACTION_ROW (row), glob ? glob : "");
button = gtk_button_new_from_icon_name ("edit-delete-symbolic");
gtk_widget_set_tooltip_text (button, _("Remove File Type"));
gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
gtk_widget_set_halign (button, GTK_ALIGN_END);
gtk_widget_add_css_class (button, "flat");
adw_action_row_add_suffix (ADW_ACTION_ROW (row), button);
g_object_set_data_full (G_OBJECT (button), "type", g_strdup (type), g_free);
g_signal_connect_object (button, "clicked", G_CALLBACK (unset_cb), self, G_CONNECT_SWAPPED);
gtk_widget_set_visible (GTK_WIDGET (self->handler_file_group), TRUE);
adw_preferences_group_add (self->handler_file_group, GTK_WIDGET (row));
self->file_handler_rows = g_list_prepend (self->file_handler_rows, row);
}
static void
add_handler_row (CcApplicationsPanel *self,
const gchar *type)
{
gtk_widget_set_visible (GTK_WIDGET (self->handler_row), TRUE);
if (g_content_type_is_a (type, "x-scheme-handler/*"))
add_scheme (self, type);
else
add_file_type (self, type);
}
static gboolean
app_info_recommended_for (GAppInfo *info,
const gchar *type)
{
/* this is horribly inefficient. I blame the mime system */
g_autolist(GObject) list = NULL;
GList *l;
gboolean ret = FALSE;
list = g_app_info_get_recommended_for_type (type);
for (l = list; l; l = l->next)
{
GAppInfo *ri = l->data;
if (g_app_info_equal (info, ri))
{
ret = TRUE;
break;
}
}
return ret;
}
static void
handler_reset_cb (CcApplicationsPanel *self)
{
const gchar **types;
gint i;
types = g_app_info_get_supported_types (self->current_app_info);
if (types == NULL || types[0] == NULL)
return;
g_signal_handler_block (self->monitor, self->monitor_id);
for (i = 0; types[i]; i++)
{
gchar *ctype = g_content_type_from_mime_type (types[i]);
g_app_info_add_supports_type (self->current_app_info, ctype, NULL);
}
g_signal_handler_unblock (self->monitor, self->monitor_id);
g_signal_emit_by_name (self->monitor, "changed");
update_handler_dialog(self, self->current_app_info);
}
static void
remove_all_handler_rows (CcApplicationsPanel *self)
{
GList *l;
for (l = self->file_handler_rows; l; l = l->next)
adw_preferences_group_remove (self->handler_file_group, l->data);
g_clear_pointer (&self->file_handler_rows, g_list_free);
for (l = self->link_handler_rows; l; l = l->next)
adw_preferences_group_remove (self->handler_link_group, l->data);
g_clear_pointer (&self->link_handler_rows, g_list_free);
}
static void
update_handler_dialog (CcApplicationsPanel *self,
GAppInfo *info)
{
g_autofree gchar *header_title = NULL;
g_autoptr(GHashTable) hash = NULL;
const gchar **types;
guint n_associations = 0;
gint i;
remove_all_handler_rows (self);
gtk_widget_set_visible (GTK_WIDGET (self->handler_row), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->handler_file_group), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->handler_link_group), FALSE);
types = g_app_info_get_supported_types (info);
if (types == NULL || types[0] == NULL)
return;
hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
gtk_widget_set_sensitive (GTK_WIDGET (self->handler_reset_button_row), FALSE);
for (i = 0; types[i]; i++)
{
g_autofree gchar *ctype = g_content_type_from_mime_type (types[i]);
if (g_hash_table_contains (hash, ctype))
continue;
if (!app_info_recommended_for (info, ctype))
{
gtk_widget_set_sensitive (GTK_WIDGET (self->handler_reset_button_row), TRUE);
continue;
}
add_handler_row (self, ctype);
g_hash_table_add (hash, g_steal_pointer (&ctype));
n_associations++;
}
if (n_associations > 0)
{
g_autofree gchar *types_number = NULL;
types_number = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE,
"%u type",
"%u types",
n_associations),
n_associations);
cc_list_row_set_secondary_label (self->handler_row, types_number);
}
header_title = g_strdup_printf (_("<b>%s</b> is used to open the following types of files and links"),
g_app_info_get_display_name (info));
adw_preferences_page_set_description (self->handler_page, header_title);
}
/* --- usage section --- */
static void
on_items_changed_cb (GListModel *list,
guint position,
guint removed,
guint added,
gpointer data)
{
CcApplicationsPanel *self = data;
if (g_list_model_get_n_items (list) == 0) {
gtk_stack_set_visible_child_name (self->main_page_stack, "no-search-results-page");
}
else if (g_list_model_get_n_items (self->app_model) == 0) {
gtk_stack_set_visible_child_name (self->main_page_stack, "no-apps-found-page");
gtk_widget_set_visible (GTK_WIDGET (self->app_search_entry), FALSE);
}
#ifdef HAVE_MALCONTENT
else if (!self->app_filter) {
gtk_stack_set_visible_child_name (self->main_page_stack, "malcontent-not-found-page");
gtk_widget_set_visible (GTK_WIDGET (self->app_search_entry), FALSE);
}
#endif
else {
gtk_stack_set_visible_child_name (self->main_page_stack, "apps-page");
}
}
static void
update_storage_page (CcApplicationsPanel *self,
GAppInfo *info)
{
g_autofree gchar *storage_page_description = NULL;
/* TRANSLATORS: %s is an app name. */
storage_page_description = g_strdup_printf (_("How much disk space <b>%s</b> is occupying with app data and caches"),
g_app_info_get_display_name (info));
adw_preferences_page_set_description (self->storage_page, storage_page_description);
}
static void
update_total_size (CcApplicationsPanel *self)
{
g_autofree gchar *formatted_size = NULL;
guint64 total;
total = self->app_size + self->data_size + self->cache_size;
formatted_size = g_format_size (total);
adw_action_row_set_subtitle (self->storage_page_total_row, formatted_size);
cc_list_row_set_secondary_label (self->storage_row, formatted_size);
}
static void
set_cache_size (GObject *source,
GAsyncResult *res,
gpointer data)
{
CcApplicationsPanel *self = data;
g_autofree gchar *formatted_size = NULL;
guint64 size;
g_autoptr(GError) error = NULL;
if (!file_size_finish (G_FILE (source), res, &size, &error))
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Failed to get flatpak cache size: %s", error->message);
return;
}
self->cache_size = size;
formatted_size = g_format_size (self->cache_size);
adw_action_row_set_subtitle (self->storage_page_cache_row, formatted_size);
gtk_widget_set_sensitive (GTK_WIDGET (self->clear_cache_button_row), self->cache_size > 0);
update_total_size (self);
}
static void
update_cache_row (CcApplicationsPanel *self,
const gchar *app_id)
{
g_autoptr(GFile) dir = get_flatpak_app_dir (app_id, "cache");
adw_action_row_set_subtitle (self->storage_page_cache_row, "");
file_size_async (dir, cc_panel_get_cancellable (CC_PANEL (self)), set_cache_size, self);
}
static void
set_data_size (GObject *source,
GAsyncResult *res,
gpointer data)
{
CcApplicationsPanel *self = data;
g_autofree gchar *formatted_size = NULL;
guint64 size;
g_autoptr(GError) error = NULL;
if (!file_size_finish (G_FILE (source), res, &size, &error))
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Failed to get flatpak data size: %s", error->message);
return;
}
self->data_size = size;
formatted_size = g_format_size (self->data_size);
adw_action_row_set_subtitle (self->storage_page_data_row, formatted_size);
update_total_size (self);
}
static void
update_data_row (CcApplicationsPanel *self,
const gchar *app_id)
{
g_autoptr(GFile) dir = get_flatpak_app_dir (app_id, "data");
adw_action_row_set_subtitle (self->storage_page_data_row, "");
file_size_async (dir, cc_panel_get_cancellable (CC_PANEL (self)), set_data_size, self);
}
static void
cache_cleared (GObject *source,
GAsyncResult *res,
gpointer data)
{
CcApplicationsPanel *self = data;
g_autoptr(GError) error = NULL;
if (!file_remove_finish (G_FILE (source), res, &error))
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Failed to remove cache: %s", error->message);
return;
}
update_cache_row (self, self->current_app_id);
}
static void
clear_cache_cb (CcApplicationsPanel *self)
{
g_autoptr(GFile) dir = NULL;
if (self->current_app_id == NULL)
return;
dir = get_flatpak_app_dir (self->current_app_id, "cache");
file_remove_async (dir, cc_panel_get_cancellable (CC_PANEL (self)), cache_cleared, self);
}
static void
update_app_row (CcApplicationsPanel *self,
const gchar *app_id)
{
g_autofree gchar *formatted_size = NULL;
if (g_str_has_prefix (app_id, PORTAL_SNAP_PREFIX))
self->app_size = get_snap_app_size (app_id + strlen (PORTAL_SNAP_PREFIX));
else
self->app_size = get_flatpak_app_size (app_id);
formatted_size = g_format_size (self->app_size);
adw_action_row_set_subtitle (self->storage_page_app_row, formatted_size);
update_total_size (self);
}
static void
update_app_sizes (CcApplicationsPanel *self,
const gchar *app_id)
{
gtk_widget_set_sensitive (GTK_WIDGET (self->clear_cache_button_row), FALSE);
self->app_size = self->data_size = self->cache_size = 0;
update_app_row (self, app_id);
update_cache_row (self, app_id);
update_data_row (self, app_id);
}
static gboolean
update_global_shortcuts_section (CcApplicationsPanel *self)
{
int global_shortcuts_count;
g_autoptr(GVariant) shortcuts = NULL;
shortcuts = g_settings_get_value (self->global_shortcuts_app_settings,
"shortcuts");
global_shortcuts_count = g_variant_n_children (shortcuts);
gtk_widget_set_visible (GTK_WIDGET (self->global_shortcuts_row),
global_shortcuts_count != 0);
return global_shortcuts_count != 0;
}
static void
update_usage_section (CcApplicationsPanel *self,
GAppInfo *info)
{
g_autofree gchar *portal_app_id = get_portal_app_id (info);
gboolean has_builtin = FALSE, has_global_shortcuts;
g_autofree char *app_path;
if (portal_app_id != NULL)
update_app_sizes (self, portal_app_id);
remove_static_permissions (self);
has_builtin = add_static_permissions (self, info, portal_app_id);
gtk_widget_set_visible (GTK_WIDGET (self->builtin_row), has_builtin);
g_clear_object (&self->global_shortcuts_app_settings);
app_path = g_strdup_printf (GLOBAL_SHORTCUTS_PATH "%s/", get_app_id (info));
self->global_shortcuts_app_settings =
g_settings_new_with_path (GLOBAL_SHORTCUTS_APP_SCHEMA, app_path);
has_global_shortcuts = update_global_shortcuts_section (self);
gtk_widget_set_visible (GTK_WIDGET (self->required_permissions_group),
has_global_shortcuts || has_builtin);
gtk_widget_set_visible (GTK_WIDGET (self->general_group), portal_app_id || has_builtin);
update_storage_page (self, info);
}
/* --- panel setup --- */
static void
update_panel (CcApplicationsPanel *self,
GtkListBoxRow *row)
{
GAppInfo *info;
if (self->perm_store == NULL)
{
/* Async permission store not initialized, row will be re-activated in the callback */
self->perm_store_pending_row = row;
return;
}
if (row == NULL)
{
g_message ("No app selected, try again");
return;
}
info = cc_applications_row_get_info (CC_APPLICATIONS_ROW (row));
adw_navigation_page_set_title (self->app_settings_page,
g_app_info_get_display_name (info));
cc_panel_push_subpage (CC_PANEL (self), self->app_settings_page);
gtk_widget_set_visible (GTK_WIDGET (self->view_details_button), gnome_software_is_installed ());
g_clear_pointer (&self->current_app_id, g_free);
g_clear_pointer (&self->current_portal_app_id, g_free);
update_header_section (self, info);
update_permissions_group (self, info);
update_handler_dialog (self, info);
update_usage_section (self, info);
g_set_object (&self->current_app_info, info);
self->current_app_id = get_app_id (info);
self->current_portal_app_id = get_portal_app_id (info);
/* Don't show the "Open" button for Settings itself. */
gtk_widget_set_visible (GTK_WIDGET (self->launch_button),
g_strcmp0 (self->current_app_id, APPLICATION_ID) != 0);
}
static gint
compare_rows (gconstpointer a,
gconstpointer b,
gpointer data)
{
GAppInfo *item1 = (GAppInfo *) a;
GAppInfo *item2 = (GAppInfo *) b;
g_autofree gchar *key1 = NULL;
g_autofree gchar *key2 = NULL;
key1 = g_utf8_casefold (g_app_info_get_display_name (item1), -1);
key2 = g_utf8_casefold (g_app_info_get_display_name (item2), -1);
return g_utf8_collate (key1, key2);
}
static void
populate_applications (CcApplicationsPanel *self)
{
g_autolist(GAppInfo) infos = NULL;
GList *l;
g_list_store_remove_all (G_LIST_STORE (self->app_model));
#ifdef HAVE_MALCONTENT
g_signal_handler_block (self->manager, self->app_filter_id);
#endif
infos = g_app_info_get_all ();
for (l = infos; l; l = l->next)
{
GAppInfo *info = l->data;
if (!g_app_info_should_show (info))
continue;
#ifdef HAVE_MALCONTENT
if (!mct_app_filter_is_appinfo_allowed (self->app_filter, info))
continue;
#endif
g_list_store_insert_sorted (G_LIST_STORE (self->app_model), info, compare_rows, NULL);
}
#ifdef HAVE_MALCONTENT
g_signal_handler_unblock (self->manager, self->app_filter_id);
#endif
}
static gboolean
filter_app_rows (GObject *item,
gpointer data)
{
CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (data);
g_autofree gchar *app_name = NULL;
g_autofree gchar *search_text = NULL;
const gchar *text;
GAppInfo *info = G_APP_INFO (item);
text = gtk_editable_get_text (GTK_EDITABLE (self->app_search_entry));
/* Only filter after the second character */
if (g_utf8_strlen (text, -1) < 2)
return TRUE;
app_name = cc_util_normalize_casefold_and_unaccent (g_app_info_get_name (info));
search_text = cc_util_normalize_casefold_and_unaccent (text);
return g_strstr_len (app_name, -1, search_text) != NULL;
}
#ifdef HAVE_MALCONTENT
static void
app_filter_changed_cb (MctAppFilter *app_filter,
uid_t uid,
CcApplicationsPanel *self)
{
populate_applications (self);
}
#endif
static void
apps_changed (CcApplicationsPanel *self)
{
populate_applications (self);
}
static void
row_activated_cb (CcApplicationsPanel *self,
GtkListBoxRow *row)
{
update_panel (self, row);
}
static void
on_perm_store_ready (GObject *source_object,
GAsyncResult *res,
gpointer data)
{
CcApplicationsPanel *self = data;
GDBusProxy *proxy;
g_autoptr(GError) error = NULL;
proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
if (proxy == NULL)
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Failed to connect to portal permission store: %s",
error->message);
return;
}
self->perm_store = proxy;
if (self->perm_store_pending_row)
g_signal_emit_by_name (self->perm_store_pending_row, "activate");
self->perm_store_pending_row = NULL;
}
static void
select_app (CcApplicationsPanel *self,
const gchar *app_id,
gboolean emit_activate)
{
GtkWidget *child;
for (child = gtk_widget_get_first_child (GTK_WIDGET (self->app_listbox));
child;
child = gtk_widget_get_next_sibling (child))
{
CcApplicationsRow *row = CC_APPLICATIONS_ROW (child);
GAppInfo *info = cc_applications_row_get_info (row);
if (g_str_has_prefix (g_app_info_get_id (info), app_id))
{
gtk_list_box_select_row (self->app_listbox, GTK_LIST_BOX_ROW (row));
if (emit_activate)
g_signal_emit_by_name (row, "activate");
break;
}
}
}
static void
on_launch_button_clicked_cb (CcApplicationsPanel *self)
{
g_autoptr(GdkAppLaunchContext) context = NULL;
g_autoptr(GError) error = NULL;
GdkDisplay *display;
if (!self->current_app_info)
return;
display = gtk_widget_get_display (GTK_WIDGET (self));
context = gdk_display_get_app_launch_context (display);
g_app_info_launch (self->current_app_info,
NULL,
G_APP_LAUNCH_CONTEXT (context),
&error);
if (error)
g_warning ("Error launching app: %s", error->message);
}
static void
on_app_search_entry_activated_cb (CcApplicationsPanel *self)
{
GtkListBoxRow *row;
row = gtk_list_box_get_row_at_y (self->app_listbox, 0);
if (!row)
return;
/* Show the app */
gtk_list_box_select_row (self->app_listbox, row);
g_signal_emit_by_name (row, "activate");
/* Cleanup the entry */
gtk_editable_set_text (GTK_EDITABLE (self->app_search_entry), "");
gtk_widget_grab_focus (GTK_WIDGET (self->app_search_entry));
}
static void
on_app_search_entry_search_changed_cb (CcApplicationsPanel *self)
{
gtk_filter_changed (self->filter, GTK_FILTER_CHANGE_DIFFERENT);
}
static void
on_app_search_entry_search_stopped_cb (CcApplicationsPanel *self)
{
gtk_editable_set_text (GTK_EDITABLE (self->app_search_entry), "");
}
static void
cc_applications_panel_dispose (GObject *object)
{
CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (object);
g_clear_pointer (&self->sandbox_info_button, gtk_widget_unparent);
remove_all_handler_rows (self);
#ifdef HAVE_SNAP
remove_snap_permissions (self);
#endif
g_clear_object (&self->monitor);
g_clear_object (&self->perm_store);
G_OBJECT_CLASS (cc_applications_panel_parent_class)->dispose (object);
}
static void
cc_applications_panel_finalize (GObject *object)
{
CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (object);
#ifdef HAVE_MALCONTENT
if (self->app_filter != NULL && self->app_filter_id != 0)
{
g_signal_handler_disconnect (self->manager, self->app_filter_id);
self->app_filter_id = 0;
}
g_clear_pointer (&self->app_filter, mct_app_filter_unref);
g_clear_object (&self->manager);
#endif
g_clear_object (&self->media_handling_settings);
g_clear_object (&self->notification_settings);
g_clear_object (&self->location_settings);
g_clear_object (&self->privacy_settings);
g_clear_object (&self->search_settings);
g_clear_object (&self->current_app_info);
g_clear_pointer (&self->current_app_id, g_free);
g_clear_pointer (&self->current_portal_app_id, g_free);
g_clear_pointer (&self->globs, g_hash_table_unref);
g_clear_pointer (&self->search_providers, g_hash_table_unref);
G_OBJECT_CLASS (cc_applications_panel_parent_class)->finalize (object);
}
static void
cc_applications_panel_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id)
{
case PROP_PARAMETERS:
{
GVariant *parameters, *v;
const gchar *first_arg = NULL;
parameters = g_value_get_variant (value);
if (parameters == NULL)
return;
if (g_variant_n_children (parameters) > 0)
{
g_variant_get_child (parameters, 0, "v", &v);
if (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING))
first_arg = g_variant_get_string (v, NULL);
else
g_warning ("Wrong type for the second argument GVariant, expected 's' but got '%s'",
(gchar *)g_variant_get_type (v));
g_variant_unref (v);
select_app (CC_APPLICATIONS_PANEL (object), first_arg, TRUE);
}
return;
}
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
cc_applications_panel_constructed (GObject *object)
{
G_OBJECT_CLASS (cc_applications_panel_parent_class)->constructed (object);
}
static void
cc_applications_panel_class_init (CcApplicationsPanelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
g_type_ensure (CC_TYPE_DEFAULT_APPS_PAGE);
g_type_ensure (CC_TYPE_LIST_ROW);
g_type_ensure (CC_TYPE_LIST_ROW_INFO_BUTTON);
g_type_ensure (CC_TYPE_REMOVABLE_MEDIA_SETTINGS);
object_class->dispose = cc_applications_panel_dispose;
object_class->finalize = cc_applications_panel_finalize;
object_class->constructed = cc_applications_panel_constructed;
object_class->set_property = cc_applications_panel_set_property;
g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters");
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-applications-panel.ui");
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage_page_app_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, app_icon_image);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, app_listbox);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, app_name_label);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, app_search_entry);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, app_settings_page);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, required_permissions_group);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, autorun_never_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin_page);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin_list);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage_page_cache_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, global_shortcuts_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, camera_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, clear_cache_button_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage_page_data_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, default_apps_page);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_page);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_file_group);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_link_group);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_reset_button_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, install_button);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, permissions_group);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, launch_button);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, location_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, main_page_stack);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, microphone_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_camera_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_location_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_microphone_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_search_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_sounds_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, notifications_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, background_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, wallpaper_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, removable_media_settings);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sandbox_banner);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sandbox_info_button);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, screenshots_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, shortcuts_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, search_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sounds_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage_page);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage_page_total_row);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, general_group);
gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, view_details_button);
gtk_widget_class_bind_template_callback (widget_class, camera_cb);
gtk_widget_class_bind_template_callback (widget_class, location_cb);
gtk_widget_class_bind_template_callback (widget_class, microphone_cb);
gtk_widget_class_bind_template_callback (widget_class, search_cb);
gtk_widget_class_bind_template_callback (widget_class, notification_cb);
gtk_widget_class_bind_template_callback (widget_class, background_cb);
gtk_widget_class_bind_template_callback (widget_class, wallpaper_cb);
gtk_widget_class_bind_template_callback (widget_class, screenshot_cb);
gtk_widget_class_bind_template_callback (widget_class, shortcuts_cb);
gtk_widget_class_bind_template_callback (widget_class, privacy_link_cb);
gtk_widget_class_bind_template_callback (widget_class, sound_cb);
gtk_widget_class_bind_template_callback (widget_class, clear_cache_cb);
gtk_widget_class_bind_template_callback (widget_class, open_software_cb);
gtk_widget_class_bind_template_callback (widget_class, handler_reset_cb);
gtk_widget_class_bind_template_callback (widget_class, global_shortcuts_cb);
gtk_widget_class_bind_template_callback (widget_class, on_launch_button_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, on_app_search_entry_activated_cb);
gtk_widget_class_bind_template_callback (widget_class, on_app_search_entry_search_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, on_app_search_entry_search_stopped_cb);
}
static GtkWidget *
app_row_new (gpointer item,
gpointer user_data)
{
GAppInfo *info = item;
return GTK_WIDGET (cc_applications_row_new (info));
}
static void
cc_applications_panel_init (CcApplicationsPanel *self)
{
#ifdef HAVE_MALCONTENT
g_autoptr(GDBusConnection) system_bus = NULL;
g_autoptr(GError) error = NULL;
#endif
g_resources_register (cc_applications_get_resource ());
gtk_widget_init_template (GTK_WIDGET (self));
gtk_widget_set_visible (GTK_WIDGET (self->install_button), gnome_software_is_installed ());
g_signal_connect_object (self->app_listbox, "row-activated",
G_CALLBACK (row_activated_cb), self, G_CONNECT_SWAPPED);
g_signal_connect_object (self->view_details_button,
"clicked",
G_CALLBACK (open_software_cb),
self,
G_CONNECT_SWAPPED);
self->filter = GTK_FILTER (gtk_custom_filter_new ((GtkCustomFilterFunc) filter_app_rows,
self, NULL));
self->app_model = G_LIST_MODEL (g_list_store_new (G_TYPE_APP_INFO));
self->filter_model = G_LIST_MODEL (gtk_filter_list_model_new (self->app_model,
GTK_FILTER (self->filter)));
g_signal_connect (self->filter_model, "items-changed",
G_CALLBACK (on_items_changed_cb), self);
gtk_list_box_bind_model (self->app_listbox,
self->filter_model,
app_row_new,
NULL,
NULL);
self->location_settings = g_settings_new ("org.gnome.system.location");
self->privacy_settings = g_settings_new ("org.gnome.desktop.privacy");
self->search_settings = g_settings_new ("org.gnome.desktop.search-providers");
self->media_handling_settings = g_settings_new ("org.gnome.desktop.media-handling");
g_settings_bind (self->media_handling_settings,
"autorun-never",
self->autorun_never_row,
"active",
G_SETTINGS_BIND_INVERT_BOOLEAN);
#ifdef HAVE_MALCONTENT
/* FIXME: should become asynchronous */
system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, self->cancellable, &error);
if (system_bus == NULL)
{
g_warning ("Error getting system bus while setting up app permissions: %s", error->message);
return;
}
/* Load the users parental controls settings too, so we can filter the list. */
self->manager = mct_manager_new (system_bus);
self->app_filter = mct_manager_get_app_filter (self->manager,
getuid (),
MCT_MANAGER_GET_VALUE_FLAGS_NONE,
self->cancellable,
&error);
if (error)
{
g_warning ("Error retrieving app filter: %s", error->message);
return;
}
self->app_filter_id = g_signal_connect (self->manager, "app-filter-changed",
G_CALLBACK (app_filter_changed_cb), self);
#endif
populate_applications (self);
self->monitor = g_app_info_monitor_get ();
self->monitor_id = g_signal_connect_object (self->monitor, "changed", G_CALLBACK (apps_changed), self, G_CONNECT_SWAPPED);
g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.freedesktop.impl.portal.PermissionStore",
"/org/freedesktop/impl/portal/PermissionStore",
"org.freedesktop.impl.portal.PermissionStore",
cc_panel_get_cancellable (CC_PANEL (self)),
on_perm_store_ready,
self);
self->globs = parse_globs ();
self->search_providers = parse_search_providers ();
}