summaryrefslogtreecommitdiffstats
path: root/panels/applications
diff options
context:
space:
mode:
Diffstat (limited to 'panels/applications')
-rw-r--r--panels/applications/applications.gresource.xml12
-rw-r--r--panels/applications/cc-action-row.c218
-rw-r--r--panels/applications/cc-action-row.h42
-rw-r--r--panels/applications/cc-action-row.ui51
-rw-r--r--panels/applications/cc-applications-panel.c2141
-rw-r--r--panels/applications/cc-applications-panel.css11
-rw-r--r--panels/applications/cc-applications-panel.h30
-rw-r--r--panels/applications/cc-applications-panel.ui608
-rw-r--r--panels/applications/cc-applications-row.c108
-rw-r--r--panels/applications/cc-applications-row.h36
-rw-r--r--panels/applications/cc-applications-row.ui32
-rw-r--r--panels/applications/cc-info-row.c215
-rw-r--r--panels/applications/cc-info-row.h37
-rw-r--r--panels/applications/cc-info-row.ui41
-rw-r--r--panels/applications/cc-snap-row.c316
-rw-r--r--panels/applications/cc-snap-row.h36
-rw-r--r--panels/applications/cc-snap-row.ui54
-rw-r--r--panels/applications/cc-toggle-row.c144
-rw-r--r--panels/applications/cc-toggle-row.h37
-rw-r--r--panels/applications/cc-toggle-row.ui30
-rw-r--r--panels/applications/globs.c62
-rw-r--r--panels/applications/globs.h29
-rw-r--r--panels/applications/gnome-applications-panel.desktop.in.in16
-rw-r--r--panels/applications/meson.build58
-rw-r--r--panels/applications/search.c133
-rw-r--r--panels/applications/search.h29
-rw-r--r--panels/applications/utils.c268
-rw-r--r--panels/applications/utils.h57
28 files changed, 4851 insertions, 0 deletions
diff --git a/panels/applications/applications.gresource.xml b/panels/applications/applications.gresource.xml
new file mode 100644
index 0000000..b77aaba
--- /dev/null
+++ b/panels/applications/applications.gresource.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/control-center/applications">
+ <file preprocess="xml-stripblanks">cc-action-row.ui</file>
+ <file preprocess="xml-stripblanks">cc-applications-panel.ui</file>
+ <file preprocess="xml-stripblanks">cc-applications-row.ui</file>
+ <file preprocess="xml-stripblanks">cc-info-row.ui</file>
+ <file preprocess="xml-stripblanks">cc-snap-row.ui</file>
+ <file preprocess="xml-stripblanks">cc-toggle-row.ui</file>
+ <file>cc-applications-panel.css</file>
+ </gresource>
+</gresources>
diff --git a/panels/applications/cc-action-row.c b/panels/applications/cc-action-row.c
new file mode 100644
index 0000000..b541f79
--- /dev/null
+++ b/panels/applications/cc-action-row.c
@@ -0,0 +1,218 @@
+/* cc-action-row.c
+ *
+ * Copyright 2018 Matthias Clasen <matthias.clasen@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
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include "cc-action-row.h"
+#include "cc-applications-resources.h"
+
+struct _CcActionRow
+{
+ GtkListBoxRow parent;
+
+ GtkWidget *title;
+ GtkWidget *subtitle;
+ GtkWidget *button;
+};
+
+G_DEFINE_TYPE (CcActionRow, cc_action_row, GTK_TYPE_LIST_BOX_ROW)
+
+static int activated_signal;
+
+enum
+{
+ PROP_0,
+ PROP_TITLE,
+ PROP_SUBTITLE,
+ PROP_ACTION,
+ PROP_ENABLED,
+ PROP_DESTRUCTIVE
+};
+
+static void
+clicked_cb (CcActionRow *row)
+{
+ g_signal_emit (row, activated_signal, 0);
+}
+
+static void
+cc_action_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcActionRow *row = CC_ACTION_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_TITLE:
+ g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->title)));
+ break;
+
+ case PROP_SUBTITLE:
+ g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->subtitle)));
+ break;
+
+ case PROP_ACTION:
+ g_value_set_string (value, gtk_button_get_label (GTK_BUTTON (row->button)));
+ break;
+
+ case PROP_ENABLED:
+ g_value_set_boolean (value, gtk_widget_get_sensitive (row->button));
+ break;
+
+ case PROP_DESTRUCTIVE:
+ g_value_set_boolean (value,
+ gtk_style_context_has_class (gtk_widget_get_style_context (row->button), "destructive-action"));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cc_action_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcActionRow *row = CC_ACTION_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_TITLE:
+ gtk_label_set_label (GTK_LABEL (row->title), g_value_get_string (value));
+ break;
+
+ case PROP_SUBTITLE:
+ gtk_label_set_label (GTK_LABEL (row->subtitle), g_value_get_string (value));
+ gtk_widget_set_visible (row->subtitle, strlen (g_value_get_string (value)) > 0);
+ break;
+
+ case PROP_ACTION:
+ gtk_button_set_label (GTK_BUTTON (row->button), g_value_get_string (value));
+ break;
+
+ case PROP_ENABLED:
+ gtk_widget_set_sensitive (row->button, g_value_get_boolean (value));
+ break;
+
+ case PROP_DESTRUCTIVE:
+ if (g_value_get_boolean (value))
+ gtk_style_context_add_class (gtk_widget_get_style_context (row->button), "destructive-action");
+ else
+ gtk_style_context_remove_class (gtk_widget_get_style_context (row->button), "destructive-action");
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cc_action_row_class_init (CcActionRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = cc_action_row_get_property;
+ object_class->set_property = cc_action_row_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_TITLE,
+ g_param_spec_string ("title", "title", "title",
+ NULL, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SUBTITLE,
+ g_param_spec_string ("subtitle", "subtitle", "subtitle",
+ NULL, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_ACTION,
+ g_param_spec_string ("action", "action", "action",
+ NULL, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_ENABLED,
+ g_param_spec_boolean ("enabled", "enabled", "enabled",
+ TRUE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_DESTRUCTIVE,
+ g_param_spec_boolean ("destructive", "destructive", "destructive",
+ FALSE, G_PARAM_READWRITE));
+
+ activated_signal = g_signal_new ("activated",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-action-row.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcActionRow, title);
+ gtk_widget_class_bind_template_child (widget_class, CcActionRow, subtitle);
+ gtk_widget_class_bind_template_child (widget_class, CcActionRow, button);
+
+ gtk_widget_class_bind_template_callback (widget_class, clicked_cb);
+}
+
+static void
+cc_action_row_init (CcActionRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcActionRow *
+cc_action_row_new (void)
+{
+ return CC_ACTION_ROW (g_object_new (CC_TYPE_ACTION_ROW, NULL));
+}
+
+void
+cc_action_row_set_title (CcActionRow *row,
+ const gchar *name)
+{
+ gtk_label_set_label (GTK_LABEL (row->title), name);
+}
+
+void
+cc_action_row_set_subtitle (CcActionRow *row,
+ const gchar *name)
+{
+ gtk_label_set_label (GTK_LABEL (row->subtitle), name);
+ gtk_widget_set_visible (row->subtitle, strlen (name) > 0);
+}
+
+void
+cc_action_row_set_action (CcActionRow *row,
+ const gchar *action,
+ gboolean sensitive)
+{
+ gtk_button_set_label (GTK_BUTTON (row->button), action);
+ gtk_widget_set_sensitive (row->button, sensitive);
+}
diff --git a/panels/applications/cc-action-row.h b/panels/applications/cc-action-row.h
new file mode 100644
index 0000000..2912adb
--- /dev/null
+++ b/panels/applications/cc-action-row.h
@@ -0,0 +1,42 @@
+/* cc-action-row.h
+ *
+ * Copyright 2018 Matthias Clasen <matthias.clasen@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
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_ACTION_ROW (cc_action_row_get_type())
+G_DECLARE_FINAL_TYPE (CcActionRow, cc_action_row, CC, ACTION_ROW, GtkListBoxRow)
+
+CcActionRow* cc_action_row_new (void);
+
+void cc_action_row_set_title (CcActionRow *row,
+ const gchar *label);
+
+void cc_action_row_set_subtitle (CcActionRow *row,
+ const gchar *label);
+
+void cc_action_row_set_action (CcActionRow *row,
+ const gchar *action,
+ gboolean sensitive);
+
+G_END_DECLS
diff --git a/panels/applications/cc-action-row.ui b/panels/applications/cc-action-row.ui
new file mode 100644
index 0000000..6a75964
--- /dev/null
+++ b/panels/applications/cc-action-row.ui
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcActionRow" parent="GtkListBoxRow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="activatable">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="border-width">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkLabel" id="title">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="hexpand">1</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="expand">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="subtitle">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="hexpand">1</property>
+ <property name="ellipsize">end</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button">
+ <property name="visible">1</property>
+ <property name="valign">center</property>
+ <signal name="clicked" handler="clicked_cb" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/applications/cc-applications-panel.c b/panels/applications/cc-applications-panel.c
new file mode 100644
index 0000000..8b632d4
--- /dev/null
+++ b/panels/applications/cc-applications-panel.c
@@ -0,0 +1,2141 @@
+/* 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_SNAP
+#include <snapd-glib/snapd-glib.h>
+#endif
+#ifdef HAVE_MALCONTENT
+#include <libmalcontent/malcontent.h>
+#endif
+
+#include <gio/gdesktopappinfo.h>
+
+#include "cc-applications-panel.h"
+#include "cc-applications-row.h"
+#include "cc-toggle-row.h"
+#include "cc-info-row.h"
+#include "cc-action-row.h"
+#include "cc-applications-resources.h"
+#include "cc-util.h"
+#ifdef HAVE_SNAP
+#include "cc-snap-row.h"
+#endif
+#include "globs.h"
+#include "list-box-helper.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 PORTAL_SNAP_PREFIX "snap."
+
+struct _CcApplicationsPanel
+{
+ CcPanel parent;
+
+ GtkBox *sidebar_box;
+ GtkListBox *sidebar_listbox;
+ GtkEntry *sidebar_search_entry;
+ GtkButton *header_button;
+ GtkLabel *title_label;
+ GAppInfoMonitor *monitor;
+ gulong monitor_id;
+#ifdef HAVE_MALCONTENT
+ GCancellable *cancellable;
+
+ MctAppFilter *app_filter;
+ MctManager *manager;
+ guint app_filter_id;
+#endif
+
+ gchar *current_app_id;
+ gchar *current_portal_app_id;
+
+ GHashTable *globs;
+ GHashTable *search_providers;
+
+ GDBusProxy *perm_store;
+ GSettings *notification_settings;
+ GSettings *location_settings;
+ GSettings *privacy_settings;
+ GSettings *search_settings;
+
+ GtkStack *stack;
+ GtkBox *empty_box;
+ GtkBox *settings_box;
+
+ GtkBox *permission_section;
+ GtkListBox *permission_list;
+ CcToggleRow *camera;
+ CcInfoRow *no_camera;
+ CcToggleRow *location;
+ CcInfoRow *no_location;
+ CcToggleRow *shortcuts;
+ CcToggleRow *microphone;
+ CcInfoRow *no_microphone;
+ CcInfoRow *builtin;
+ GtkDialog *builtin_dialog;
+ GtkLabel *builtin_label;
+ GtkListBox *builtin_list;
+
+ GtkBox *integration_section;
+ GtkListBox *integration_list;
+ CcToggleRow *notification;
+ CcToggleRow *background;
+ CcToggleRow *wallpaper;
+ CcToggleRow *sound;
+ CcInfoRow *no_sound;
+ CcToggleRow *search;
+ CcInfoRow *no_search;
+
+ GtkBox *handler_section;
+ GtkButton *handler_reset;
+ GtkListBox *handler_list;
+ CcInfoRow *hypertext;
+ CcInfoRow *text;
+ CcInfoRow *images;
+ CcInfoRow *fonts;
+ CcInfoRow *archives;
+ CcInfoRow *packages;
+ CcInfoRow *audio;
+ CcInfoRow *video;
+ CcInfoRow *other;
+ CcInfoRow *link;
+
+ GtkBox *usage_section;
+ GtkListBox *usage_list;
+ CcInfoRow *storage;
+ GtkDialog *storage_dialog;
+ GtkListBox *storage_list;
+ CcInfoRow *app;
+ CcInfoRow *data;
+ CcInfoRow *cache;
+ CcInfoRow *total;
+ GtkButton *clear_cache_button;
+
+ guint64 app_size;
+ guint64 cache_size;
+ guint64 data_size;
+};
+
+static void select_app (CcApplicationsPanel *self,
+ const gchar *app_id);
+
+G_DEFINE_TYPE (CcApplicationsPanel, cc_applications_panel, CC_TYPE_PANEL)
+
+enum
+{
+ PROP_0,
+ PROP_PARAMETERS
+};
+
+/* 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 };
+
+ if (self->current_app_id == NULL)
+ argv[1] = NULL;
+ else
+ argv[2] = self->current_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,
+ cc_toggle_row_get_allowed (self->search));
+}
+
+/* --- 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 || strcmp (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, cc_toggle_row_get_allowed (self->notification));
+}
+
+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 || strcmp (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, cc_toggle_row_get_allowed (self->background));
+}
+
+/* --- 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 || strcmp (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, cc_toggle_row_get_allowed (self->wallpaper));
+}
+
+/* --- 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) && 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/master/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, cc_toggle_row_get_allowed (self->shortcuts));
+}
+
+/* --- 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 || strcmp (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", cc_toggle_row_get_allowed (self->microphone));
+}
+
+static void
+sound_cb (CcApplicationsPanel *self)
+{
+ if (self->current_portal_app_id)
+ set_device_allowed (self, "speakers", cc_toggle_row_get_allowed (self->sound));
+}
+
+static void
+camera_cb (CcApplicationsPanel *self)
+{
+ if (self->current_portal_app_id)
+ set_device_allowed (self, "camera", cc_toggle_row_get_allowed (self->camera));
+}
+
+/* --- 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 || strcmp (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, cc_toggle_row_get_allowed (self->location));
+}
+
+/* --- permissions section --- */
+
+#ifdef HAVE_SNAP
+static void
+remove_snap_permissions (CcApplicationsPanel *self)
+{
+ g_autoptr(GList) rows = NULL;
+ GList *link;
+
+ rows = gtk_container_get_children (GTK_CONTAINER (self->permission_list));
+ for (link = rows; link; link = link->next)
+ {
+ GtkWidget *row = link->data;
+
+ if (row == GTK_WIDGET (self->builtin))
+ break;
+
+ if (CC_IS_SNAP_ROW (row))
+ gtk_container_remove (GTK_CONTAINER (self->permission_list), GTK_WIDGET (row));
+ }
+}
+
+static gboolean
+add_snap_permissions (CcApplicationsPanel *self,
+ GAppInfo *info,
+ const gchar *app_id)
+{
+ const gchar *snap_name;
+ g_autoptr(GList) rows = NULL;
+ gint index;
+ g_autoptr(SnapdClient) client = NULL;
+ g_autoptr(GPtrArray) interfaces = NULL;
+ g_autoptr(GPtrArray) plugs = NULL;
+ g_autoptr(GPtrArray) slots = NULL;
+ SnapdInterface *interface = 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);
+
+ rows = gtk_container_get_children (GTK_CONTAINER (self->permission_list));
+ index = g_list_index (rows, self->builtin);
+ g_assert (index >= 0);
+
+ client = snapd_client_new ();
+
+ interfaces = snapd_client_get_interfaces2_sync (client,
+ SNAPD_GET_INTERFACES_FLAGS_NONE,
+ NULL,
+ NULL, &interfaces_error);
+ if (interfaces == NULL)
+ g_warning ("Failed to get snap interfaces: %s", interfaces_error->message);
+
+ if (!snapd_client_get_connections2_sync (client,
+ SNAPD_GET_CONNECTIONS_FLAGS_SELECT_ALL,
+ NULL, NULL,
+ NULL, NULL,
+ &plugs, &slots,
+ NULL, &error))
+ {
+ g_warning ("Failed to get snap connections: %s", error->message);
+ return FALSE;
+ }
+
+ for (int i = 0; i < plugs->len; i++)
+ {
+ SnapdPlug *plug = g_ptr_array_index (plugs, i);
+ CcSnapRow *row;
+ g_autoptr(GPtrArray) 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 (snapd_plug_get_snap (plug), snap_name) != 0)
+ continue;
+
+ /* Ignore interfaces that are too low level to make sense to show or disable */
+ if (g_strv_contains (hidden_interfaces, snapd_plug_get_interface (plug)))
+ continue;
+
+ available_slots = g_ptr_array_new_with_free_func (g_object_unref);
+ for (int j = 0; j < slots->len; j++)
+ {
+ SnapdSlot *slot = g_ptr_array_index (slots, j);
+ if (g_strcmp0 (snapd_plug_get_interface (plug), snapd_slot_get_interface (slot)) != 0)
+ continue;
+
+ g_ptr_array_add (available_slots, g_object_ref (slot));
+ }
+
+ if (interfaces != NULL)
+ {
+ for (int j = 0; j < interfaces->len; j++)
+ {
+ SnapdInterface *i = g_ptr_array_index (interfaces, j);
+ if (g_strcmp0 (snapd_interface_get_name (i), snapd_plug_get_interface (plug)) == 0)
+ interface = i;
+ }
+ }
+
+ row = cc_snap_row_new (cc_panel_get_cancellable (CC_PANEL (self)), interface, plug, available_slots);
+ gtk_widget_show (GTK_WIDGET (row));
+ gtk_list_box_insert (GTK_LIST_BOX (self->permission_list), GTK_WIDGET (row), index);
+ index++;
+ added++;
+ }
+
+ return added > 0;
+}
+#endif
+
+static gint
+add_static_permission_row (CcApplicationsPanel *self,
+ const gchar *title,
+ const gchar *subtitle)
+{
+ GtkWidget *row;
+
+ row = g_object_new (CC_TYPE_INFO_ROW,
+ "title", title,
+ "info", subtitle,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (self->builtin_list), row);
+
+ return 1;
+}
+
+static void
+permission_row_activated_cb (CcApplicationsPanel *self,
+ GtkListBoxRow *list_row)
+{
+ if (list_row == GTK_LIST_BOX_ROW (self->builtin))
+ {
+ gtk_window_set_transient_for (GTK_WINDOW (self->builtin_dialog),
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))));
+ gtk_window_present (GTK_WINDOW (self->builtin_dialog));
+ }
+}
+
+static gboolean
+add_static_permissions (CcApplicationsPanel *self,
+ GAppInfo *info,
+ const gchar *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;
+ gint added = 0;
+ g_autofree gchar *text = NULL;
+
+ if (!g_str_has_prefix (app_id, PORTAL_SNAP_PREFIX))
+ keyfile = get_flatpak_metadata (app_id);
+ 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 (_("%s has the following permissions built-in. These cannot be altered. If you are concerned about these permissions, consider removing this application."), g_app_info_get_display_name (info));
+ gtk_label_set_label (self->builtin_label, text);
+
+ return added > 0;
+}
+
+static void
+remove_static_permissions (CcApplicationsPanel *self)
+{
+ container_remove_all (GTK_CONTAINER (self->builtin_list));
+}
+
+static void
+update_permission_section (CcApplicationsPanel *self,
+ GAppInfo *info)
+{
+ g_autofree gchar *portal_app_id = get_portal_app_id (info);
+ gboolean disabled, allowed, set;
+ gboolean has_any = FALSE, has_builtin = FALSE;
+
+ if (portal_app_id == NULL)
+ {
+ gtk_widget_hide (GTK_WIDGET (self->permission_section));
+ return;
+ }
+
+ disabled = g_settings_get_boolean (self->privacy_settings, "disable-camera");
+ get_device_allowed (self, "camera", portal_app_id, &set, &allowed);
+ cc_toggle_row_set_allowed (self->camera, allowed);
+ gtk_widget_set_visible (GTK_WIDGET (self->camera), set && !disabled);
+ gtk_widget_set_visible (GTK_WIDGET (self->no_camera), 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);
+ cc_toggle_row_set_allowed (self->microphone, allowed);
+ gtk_widget_set_visible (GTK_WIDGET (self->microphone), set && !disabled);
+ gtk_widget_set_visible (GTK_WIDGET (self->no_microphone), set && disabled);
+ has_any |= set;
+
+ disabled = !g_settings_get_boolean (self->location_settings, "enabled");
+ get_location_allowed (self, portal_app_id, &set, &allowed);
+ cc_toggle_row_set_allowed (self->location, allowed);
+ gtk_widget_set_visible (GTK_WIDGET (self->location), set && !disabled);
+ gtk_widget_set_visible (GTK_WIDGET (self->no_location), set && disabled);
+ has_any |= set;
+
+#ifdef HAVE_SNAP
+ remove_snap_permissions (self);
+ has_any |= add_snap_permissions (self, info, portal_app_id);
+#endif
+
+ remove_static_permissions (self);
+ has_builtin = add_static_permissions (self, info, portal_app_id);
+ gtk_widget_set_visible (GTK_WIDGET (self->builtin), has_builtin);
+ has_any |= has_builtin;
+
+ gtk_widget_set_visible (GTK_WIDGET (self->permission_section), has_any);
+}
+
+/* --- gintegration section --- */
+
+static void
+update_integration_section (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);
+ cc_toggle_row_set_allowed (self->search, allowed);
+ gtk_widget_set_visible (GTK_WIDGET (self->search), set && !disabled);
+ gtk_widget_set_visible (GTK_WIDGET (self->no_search), 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), set);
+ cc_toggle_row_set_allowed (self->shortcuts, allowed);
+ }
+ else
+ {
+ gtk_widget_hide (GTK_WIDGET (self->shortcuts));
+ }
+
+ if (portal_app_id != NULL)
+ {
+ g_clear_object (&self->notification_settings);
+ get_notification_allowed (self, portal_app_id, &set, &allowed);
+ cc_toggle_row_set_allowed (self->notification, allowed);
+ gtk_widget_set_visible (GTK_WIDGET (self->notification), set);
+ has_any |= set;
+
+ get_background_allowed (self, portal_app_id, &set, &allowed);
+ cc_toggle_row_set_allowed (self->background, allowed);
+ gtk_widget_set_visible (GTK_WIDGET (self->background), set);
+ has_any |= set;
+
+ get_wallpaper_allowed (self, portal_app_id, &set, &allowed);
+ cc_toggle_row_set_allowed (self->wallpaper, allowed);
+ gtk_widget_set_visible (GTK_WIDGET (self->wallpaper), 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);
+ cc_toggle_row_set_allowed (self->sound, allowed);
+ gtk_widget_set_visible (GTK_WIDGET (self->sound), set && !disabled);
+ gtk_widget_set_visible (GTK_WIDGET (self->no_sound), set && disabled);
+ }
+ else
+ {
+ g_set_object (&self->notification_settings, get_notification_settings (app_id));
+ get_notification_allowed (self, app_id, &set, &allowed);
+ cc_toggle_row_set_allowed (self->notification, allowed);
+ gtk_widget_set_visible (GTK_WIDGET (self->notification), set);
+ has_any |= set;
+
+ gtk_widget_hide (GTK_WIDGET (self->background));
+ gtk_widget_hide (GTK_WIDGET (self->wallpaper));
+ gtk_widget_hide (GTK_WIDGET (self->sound));
+ gtk_widget_hide (GTK_WIDGET (self->no_sound));
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (self->integration_section), has_any);
+}
+
+/* --- handler section --- */
+
+static void
+unset_cb (CcApplicationsPanel *self,
+ CcActionRow *row)
+{
+ const gchar *type;
+ GtkListBoxRow *selected;
+ GAppInfo *info;
+
+ selected = gtk_list_box_get_selected_row (self->sidebar_listbox);
+ info = cc_applications_row_get_info (CC_APPLICATIONS_ROW (selected));
+
+ type = (const gchar *)g_object_get_data (G_OBJECT (row), "type");
+
+ g_app_info_remove_supports_type (info, type, NULL);
+}
+
+static void
+update_group_row_count (CcInfoRow *row,
+ gint delta)
+{
+ gint count;
+ g_autofree gchar *text = NULL;
+
+ count = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "count"));
+ count += delta;
+ g_object_set_data (G_OBJECT (row), "count", GINT_TO_POINTER (count));
+ text = g_strdup_printf ("%d", count);
+ g_object_set (row, "info", text, NULL);
+}
+
+static void
+add_scheme (CcApplicationsPanel *self,
+ CcInfoRow *after,
+ const gchar *type)
+{
+ CcActionRow *row = NULL;
+ gint pos;
+
+ if (g_str_has_suffix (type, "http"))
+ {
+ row = cc_action_row_new ();
+ cc_action_row_set_title (row, _("Web Links"));
+ cc_action_row_set_subtitle (row, "http://, https://");
+ }
+ else if (g_str_has_suffix (type, "https"))
+ {
+ return; /* assume anything that handles https also handles http */
+ }
+ else if (g_str_has_suffix (type, "git"))
+ {
+ row = cc_action_row_new ();
+ cc_action_row_set_title (row, _("Git Links"));
+ cc_action_row_set_subtitle (row, "git://");
+ }
+ else
+ {
+ gchar *scheme = strrchr (type, '/') + 1;
+ g_autofree gchar *title = g_strdup_printf (_("%s Links"), scheme);
+ g_autofree gchar *subtitle = g_strdup_printf ("%s://", scheme);
+
+ row = cc_action_row_new ();
+ cc_action_row_set_title (row, title);
+ cc_action_row_set_subtitle (row, subtitle);
+ }
+
+ cc_action_row_set_action (row, _("Unset"), TRUE);
+ g_object_set_data_full (G_OBJECT (row), "type", g_strdup (type), g_free);
+ g_signal_connect_object (row,
+ "activated",
+ G_CALLBACK (unset_cb),
+ self, G_CONNECT_SWAPPED);
+
+ if (after)
+ {
+ pos = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (after)) + 1;
+ g_object_bind_property (after, "expanded",
+ row, "visible",
+ G_BINDING_SYNC_CREATE);
+ }
+ else
+ pos = -1;
+ gtk_list_box_insert (self->handler_list, GTK_WIDGET (row), pos);
+ update_group_row_count (after, 1);
+}
+
+static void
+add_file_type (CcApplicationsPanel *self,
+ CcInfoRow *after,
+ const gchar *type)
+{
+ CcActionRow *row;
+ g_autofree gchar *desc = NULL;
+ gint pos;
+ const gchar *glob;
+
+ glob = g_hash_table_lookup (self->globs, type);
+
+ desc = g_content_type_get_description (type);
+ row = cc_action_row_new ();
+ cc_action_row_set_title (row, desc);
+ cc_action_row_set_subtitle (row, glob ? glob : "");
+ cc_action_row_set_action (row, _("Unset"), TRUE);
+ g_object_set_data_full (G_OBJECT (row), "type", g_strdup (type), g_free);
+ g_signal_connect_object (row, "activated", G_CALLBACK (unset_cb), self, G_CONNECT_SWAPPED);
+
+ if (after)
+ {
+ pos = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (after)) + 1;
+ g_object_bind_property (after, "expanded",
+ row, "visible",
+ G_BINDING_SYNC_CREATE);
+ }
+ else
+ {
+ pos = -1;
+ }
+
+ gtk_list_box_insert (self->handler_list, GTK_WIDGET (row), pos);
+ update_group_row_count (after, 1);
+}
+
+static gboolean
+is_hypertext_type (const gchar *type)
+{
+ const gchar *types[] = {
+ "text/html",
+ "text/htmlh",
+ "text/xml",
+ "application/xhtml+xml",
+ "application/vnd.mozilla.xul+xml",
+ "text/mml",
+ NULL
+ };
+ return g_strv_contains (types, type);
+}
+
+static void
+ensure_group_row (CcApplicationsPanel *self,
+ CcInfoRow **row,
+ const gchar *title)
+{
+ if (*row == NULL)
+ {
+ CcInfoRow *r = CC_INFO_ROW (g_object_new (CC_TYPE_INFO_ROW,
+ "title", title,
+ "has-expander", TRUE,
+ NULL));
+ gtk_list_box_insert (self->handler_list, GTK_WIDGET (r), -1);
+ *row = r;
+ }
+}
+
+static void
+add_link_type (CcApplicationsPanel *self,
+ const gchar *type)
+{
+ ensure_group_row (self, &self->link, _("Links"));
+ add_scheme (self, self->link, type);
+}
+
+static void
+add_hypertext_type (CcApplicationsPanel *self,
+ const gchar *type)
+{
+ ensure_group_row (self, &self->hypertext, _("Hypertext Files"));
+ add_file_type (self, self->hypertext, type);
+}
+
+static gboolean
+is_text_type (const gchar *type)
+{
+ return g_content_type_is_a (type, "text/*");
+}
+
+static void
+add_text_type (CcApplicationsPanel *self,
+ const gchar *type)
+{
+ ensure_group_row (self, &self->text, _("Text Files"));
+ add_file_type (self, self->text, type);
+}
+
+static gboolean
+is_image_type (const gchar *type)
+{
+ return g_content_type_is_a (type, "image/*");
+}
+
+static void
+add_image_type (CcApplicationsPanel *self,
+ const gchar *type)
+{
+ ensure_group_row (self, &self->images, _("Image Files"));
+ add_file_type (self, self->images, type);
+}
+
+static gboolean
+is_font_type (const gchar *type)
+{
+ return g_content_type_is_a (type, "font/*") ||
+ g_str_equal (type, "application/x-font-pcf") ||
+ g_str_equal (type, "application/x-font-type1");
+}
+
+static void
+add_font_type (CcApplicationsPanel *self,
+ const gchar *type)
+{
+ ensure_group_row (self, &self->fonts, _("Font Files"));
+ add_file_type (self, self->fonts, type);
+}
+
+static gboolean
+is_archive_type (const gchar *type)
+{
+ const gchar *types[] = {
+ "application/bzip2",
+ "application/zip",
+ "application/x-xz-compressed-tar",
+ "application/x-xz",
+ "application/x-xar",
+ "application/x-tarz",
+ "application/x-tar",
+ "application/x-lzma-compressed-tar",
+ "application/x-lzma",
+ "application/x-lzip-compressed-tar",
+ "application/x-lzip",
+ "application/x-lha",
+ "application/gzip",
+ "application/x-cpio",
+ "application/x-compressed-tar",
+ "application/x-compress",
+ "application/x-bzip-compressed-tar",
+ "application/x-bzip",
+ "application/x-7z-compressed-tar",
+ "application/x-7z-compressed",
+ "application/x-zoo",
+ "application/x-war",
+ "application/x-stuffit",
+ "application/x-rzip-compressed-tar",
+ "application/x-rzip",
+ "application/vnd.rar",
+ "application/x-lzop-compressed-tar",
+ "application/x-lzop",
+ "application/x-lz4-compressed-tar",
+ "application/x-lz4",
+ "application/x-lrzip-compressed-tar",
+ "application/x-lrzip",
+ "application/x-lhz",
+ "application/x-java-archive",
+ "application/x-ear",
+ "application/x-cabinet",
+ "application/x-bzip1-compressed-tar",
+ "application/x-bzip1",
+ "application/x-arj",
+ "application/x-archive",
+ "application/x-ar",
+ "application/x-alz",
+ "application/x-ace",
+ "application/vnd.ms-cab-compressed",
+ NULL
+ };
+ return g_strv_contains (types, type);
+}
+
+static void
+add_archive_type (CcApplicationsPanel *self,
+ const gchar *type)
+{
+ ensure_group_row (self, &self->archives, _("Archive Files"));
+ add_file_type (self, self->archives, type);
+}
+
+static gboolean
+is_package_type (const gchar *type)
+{
+ const gchar *types[] = {
+ "application/x-source-rpm",
+ "application/x-rpm",
+ "application/vnd.debian.binary-package",
+ NULL
+ };
+ return g_strv_contains (types, type);
+}
+
+static void
+add_package_type (CcApplicationsPanel *self,
+ const gchar *type)
+{
+ ensure_group_row (self, &self->packages, _("Package Files"));
+ add_file_type (self, self->packages, type);
+}
+
+static gboolean
+is_audio_type (const gchar *type)
+{
+ return g_content_type_is_a (type, "audio/*") ||
+ g_str_equal (type, "application/ogg") ||
+ g_str_equal (type, "application/x-shorten") ||
+ g_str_equal (type, "application/x-matroska") ||
+ g_str_equal (type, "application/x-flac") ||
+ g_str_equal (type, "application/x-extension-mp4") ||
+ g_str_equal (type, "application/x-extension-m4a") ||
+ g_str_equal (type, "application/vnd.rn-realmedia") ||
+ g_str_equal (type, "application/ram") ||
+ g_str_equal (type, "application/vnd.ms-wpl");
+}
+
+static void
+add_audio_type (CcApplicationsPanel *self,
+ const gchar *type)
+{
+ ensure_group_row (self, &self->audio, _("Audio Files"));
+ add_file_type (self, self->audio, type);
+}
+
+static gboolean
+is_video_type (const gchar *type)
+{
+ return g_content_type_is_a (type, "video/*") ||
+ g_str_equal (type, "application/x-smil") ||
+ g_str_equal (type, "application/vnd.ms-asf") ||
+ g_str_equal (type, "application/mxf");
+}
+
+static void
+add_video_type (CcApplicationsPanel *self,
+ const gchar *type)
+{
+ ensure_group_row (self, &self->video, _("Video Files"));
+ add_file_type (self, self->video, type);
+}
+
+static void
+add_other_type (CcApplicationsPanel *self,
+ const gchar *type)
+{
+ ensure_group_row (self, &self->other, _("Other Files"));
+ add_file_type (self, self->other, type);
+}
+
+static void
+add_handler_row (CcApplicationsPanel *self,
+ const gchar *type)
+{
+ gtk_widget_show (GTK_WIDGET (self->handler_section));
+
+ if (g_content_type_is_a (type, "x-scheme-handler/*"))
+ add_link_type (self, type);
+ else if (is_hypertext_type (type))
+ add_hypertext_type (self, type);
+ else if (is_font_type (type))
+ add_font_type (self, type);
+ else if (is_package_type (type))
+ add_package_type (self, type);
+ else if (is_audio_type (type))
+ add_audio_type (self, type);
+ else if (is_video_type (type))
+ add_video_type (self, type);
+ else if (is_archive_type (type))
+ add_archive_type (self, type);
+ else if (is_text_type (type))
+ add_text_type (self, type);
+ else if (is_image_type (type))
+ add_image_type (self, type);
+ else
+ add_other_type (self, type);
+}
+
+static void
+handler_row_activated_cb (CcApplicationsPanel *self,
+ GtkListBoxRow *list_row)
+{
+ CcInfoRow *row;
+
+ if (!CC_IS_INFO_ROW (list_row))
+ return;
+
+ row = CC_INFO_ROW (list_row);
+ if (row == self->hypertext ||
+ row == self->text ||
+ row == self->images ||
+ row == self->fonts ||
+ row == self->archives ||
+ row == self->packages ||
+ row == self->audio ||
+ row == self->video ||
+ row == self->other ||
+ row == self->link)
+ {
+ cc_info_row_set_expanded (row, !cc_info_row_get_expanded (row));
+ }
+}
+
+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)
+{
+ GtkListBoxRow *selected;
+ GAppInfo *info;
+ const gchar **types;
+ gint i;
+
+ selected = gtk_list_box_get_selected_row (self->sidebar_listbox);
+ info = cc_applications_row_get_info (CC_APPLICATIONS_ROW (selected));
+
+ types = g_app_info_get_supported_types (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 (info, ctype, NULL);
+ }
+ g_signal_handler_unblock (self->monitor, self->monitor_id);
+ g_signal_emit_by_name (self->monitor, "changed");
+}
+
+static void
+update_handler_sections (CcApplicationsPanel *self,
+ GAppInfo *info)
+{
+ g_autoptr(GHashTable) hash = NULL;
+ const gchar **types;
+ gint i;
+
+ container_remove_all (GTK_CONTAINER (self->handler_list));
+
+ self->hypertext = NULL;
+ self->text = NULL;
+ self->images = NULL;
+ self->fonts = NULL;
+ self->archives = NULL;
+ self->packages = NULL;
+ self->audio = NULL;
+ self->video = NULL;
+ self->other = NULL;
+ self->link = NULL;
+
+ gtk_widget_hide (GTK_WIDGET (self->handler_section));
+
+ 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), 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), TRUE);
+ continue;
+ }
+
+ add_handler_row (self, ctype);
+ g_hash_table_add (hash, g_steal_pointer (&ctype));
+ }
+}
+
+/* --- usage section --- */
+
+static void
+storage_row_activated_cb (CcApplicationsPanel *self,
+ GtkListBoxRow *list_row)
+{
+ if (list_row == GTK_LIST_BOX_ROW (self->storage))
+ {
+ gtk_window_set_transient_for (GTK_WINDOW (self->storage_dialog),
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))));
+ gtk_window_present (GTK_WINDOW (self->storage_dialog));
+ }
+}
+
+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);
+ g_object_set (self->total, "info", formatted_size, NULL);
+ g_object_set (self->storage, "info", formatted_size, NULL);
+}
+
+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);
+ g_object_set (self->cache, "info", formatted_size, NULL);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->clear_cache_button), 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");
+ g_object_set (self->cache, "info", "...", NULL);
+ 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);
+ g_object_set (self->data, "info", formatted_size, NULL);
+
+ 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");
+
+ g_object_set (self->data, "info", "...", NULL);
+ 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);
+ g_object_set (self->app, "info", formatted_size, NULL);
+ 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), 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 void
+update_usage_section (CcApplicationsPanel *self,
+ GAppInfo *info)
+{
+ g_autofree gchar *portal_app_id = get_portal_app_id (info);
+
+ if (portal_app_id != NULL)
+ {
+ gtk_widget_show (GTK_WIDGET (self->usage_section));
+ update_app_sizes (self, portal_app_id);
+ }
+ else
+ {
+ gtk_widget_hide (GTK_WIDGET (self->usage_section));
+ }
+}
+
+/* --- panel setup --- */
+
+static void
+update_panel (CcApplicationsPanel *self,
+ GtkListBoxRow *row)
+{
+ GAppInfo *info;
+
+ if (self->perm_store == NULL)
+ {
+ g_message ("No permissions store proxy yet, come back later");
+ return;
+ }
+
+ if (row == NULL)
+ {
+ gtk_label_set_label (self->title_label, _("Applications"));
+ gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->empty_box));
+ gtk_widget_hide (GTK_WIDGET (GTK_WIDGET (self->header_button)));
+ return;
+ }
+
+ info = cc_applications_row_get_info (CC_APPLICATIONS_ROW (row));
+
+ gtk_label_set_label (self->title_label, g_app_info_get_display_name (info));
+ gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->settings_box));
+ gtk_widget_show (GTK_WIDGET (self->header_button));
+
+ g_clear_pointer (&self->current_app_id, g_free);
+ g_clear_pointer (&self->current_portal_app_id, g_free);
+
+ update_permission_section (self, info);
+ update_integration_section (self, info);
+ update_handler_sections (self, info);
+ update_usage_section (self, info);
+
+ self->current_app_id = get_app_id (info);
+ self->current_portal_app_id = get_portal_app_id (info);
+}
+
+static void
+populate_applications (CcApplicationsPanel *self)
+{
+ g_autolist(GObject) infos = NULL;
+ GList *l;
+
+ container_remove_all (GTK_CONTAINER (self->sidebar_listbox));
+#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;
+ GtkWidget *row;
+ g_autofree gchar *id = NULL;
+
+ if (!g_app_info_should_show (info))
+ continue;
+
+#ifdef HAVE_MALCONTENT
+ if (!mct_app_filter_is_appinfo_allowed (self->app_filter, info))
+ continue;
+#endif
+
+ row = GTK_WIDGET (cc_applications_row_new (info));
+ gtk_list_box_insert (self->sidebar_listbox, row, -1);
+
+ id = get_app_id (info);
+ if (g_strcmp0 (id, self->current_app_id) == 0)
+ gtk_list_box_select_row (self->sidebar_listbox, GTK_LIST_BOX_ROW (row));
+ }
+#ifdef HAVE_MALCONTENT
+ g_signal_handler_unblock (self->manager, self->app_filter_id);
+#endif
+}
+
+static gint
+compare_rows (GtkListBoxRow *row1,
+ GtkListBoxRow *row2,
+ gpointer data)
+{
+ const gchar *key1 = cc_applications_row_get_sort_key (CC_APPLICATIONS_ROW (row1));
+ const gchar *key2 = cc_applications_row_get_sort_key (CC_APPLICATIONS_ROW (row2));
+
+ return strcmp (key1, key2);
+}
+
+static gboolean
+filter_sidebar_rows (GtkListBoxRow *row,
+ gpointer data)
+{
+ CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (data);
+ g_autofree gchar *app_name = NULL;
+ g_autofree gchar *search_text = NULL;
+ GAppInfo *info;
+
+ /* Only filter after the second character */
+ if (gtk_entry_get_text_length (self->sidebar_search_entry) < 2)
+ return TRUE;
+
+ info = cc_applications_row_get_info (CC_APPLICATIONS_ROW (row));
+ app_name = cc_util_normalize_casefold_and_unaccent (g_app_info_get_name (info));
+ search_text = cc_util_normalize_casefold_and_unaccent (gtk_entry_get_text (self->sidebar_search_entry));
+
+ 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);
+ g_signal_emit_by_name (self, "sidebar-activated");
+}
+
+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;
+
+ update_panel (self, gtk_list_box_get_selected_row (self->sidebar_listbox));
+}
+
+static void
+select_app (CcApplicationsPanel *self,
+ const gchar *app_id)
+{
+ g_autoptr(GList) children = NULL;
+ GList *l;
+
+ children = gtk_container_get_children (GTK_CONTAINER (self->sidebar_listbox));
+ for (l = children; l; l = l->next)
+ {
+ CcApplicationsRow *row = CC_APPLICATIONS_ROW (l->data);
+ 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->sidebar_listbox, GTK_LIST_BOX_ROW (row));
+ break;
+ }
+ }
+}
+
+static void
+on_sidebar_search_entry_activated_cb (CcApplicationsPanel *self)
+{
+ GtkListBoxRow *row;
+
+ row = gtk_list_box_get_row_at_y (self->sidebar_listbox, 0);
+
+ if (!row)
+ return;
+
+ /* Show the app */
+ gtk_list_box_select_row (self->sidebar_listbox, row);
+ g_signal_emit_by_name (row, "activate");
+
+ /* Cleanup the entry */
+ gtk_entry_set_text (self->sidebar_search_entry, "");
+ gtk_widget_grab_focus (GTK_WIDGET (self->sidebar_search_entry));
+}
+
+static void
+on_sidebar_search_entry_search_changed_cb (CcApplicationsPanel *self)
+{
+ gtk_list_box_invalidate_filter (self->sidebar_listbox);
+}
+
+static void
+on_sidebar_search_entry_search_stopped_cb (CcApplicationsPanel *self)
+{
+ gtk_entry_set_text (self->sidebar_search_entry, "");
+}
+
+static void
+cc_applications_panel_dispose (GObject *object)
+{
+ CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (object);
+
+ 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->notification_settings);
+ g_clear_object (&self->location_settings);
+ g_clear_object (&self->privacy_settings);
+ g_clear_object (&self->search_settings);
+
+ 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);
+ }
+
+ return;
+ }
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cc_applications_panel_constructed (GObject *object)
+{
+ CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (object);
+ CcShell *shell;
+
+ G_OBJECT_CLASS (cc_applications_panel_parent_class)->constructed (object);
+
+ shell = cc_panel_get_shell (CC_PANEL (self));
+ cc_shell_embed_widget_in_header (shell, GTK_WIDGET (self->header_button), GTK_POS_RIGHT);
+}
+
+static GtkWidget*
+cc_applications_panel_get_sidebar_widget (CcPanel *panel)
+{
+ CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (panel);
+ return GTK_WIDGET (self->sidebar_box);
+}
+
+static GtkWidget *
+cc_applications_panel_get_title_widget (CcPanel *panel)
+{
+ CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (panel);
+ return GTK_WIDGET (self->title_label);
+}
+
+static void
+cc_applications_panel_class_init (CcApplicationsPanelClass *klass)
+{
+ CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ 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;
+
+ panel_class->get_sidebar_widget = cc_applications_panel_get_sidebar_widget;
+ panel_class->get_title_widget = cc_applications_panel_get_title_widget;
+
+ 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, app);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin_dialog);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin_label);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin_list);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, cache);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, camera);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, clear_cache_button);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, data);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, empty_box);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, header_button);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_section);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_reset);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_list);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, integration_list);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, integration_section);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, location);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, microphone);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_camera);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_location);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_microphone);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_search);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_sound);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, notification);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, background);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, wallpaper);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, shortcuts);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, permission_section);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, permission_list);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sidebar_box);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sidebar_listbox);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sidebar_search_entry);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, search);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, settings_box);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sound);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, stack);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage_dialog);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage_list);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, title_label);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, total);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, usage_list);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, usage_section);
+
+ 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, 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, permission_row_activated_cb);
+ gtk_widget_class_bind_template_callback (widget_class, handler_row_activated_cb);
+ gtk_widget_class_bind_template_callback (widget_class, clear_cache_cb);
+ gtk_widget_class_bind_template_callback (widget_class, storage_row_activated_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, on_sidebar_search_entry_activated_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_sidebar_search_entry_search_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_sidebar_search_entry_search_stopped_cb);
+}
+
+static void
+cc_applications_panel_init (CcApplicationsPanel *self)
+{
+ g_autoptr(GtkStyleProvider) provider = NULL;
+ GtkListBoxRow *row;
+#ifdef HAVE_MALCONTENT
+ g_autoptr(GDBusConnection) system_bus = NULL;
+ g_autoptr(GError) error = NULL;
+#endif
+
+ g_resources_register (cc_applications_get_resource ());
+
+ g_type_ensure(CC_TYPE_TOGGLE_ROW);
+ g_type_ensure(CC_TYPE_INFO_ROW);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ provider = GTK_STYLE_PROVIDER (gtk_css_provider_new ());
+ gtk_css_provider_load_from_resource (GTK_CSS_PROVIDER (provider),
+ "/org/gnome/control-center/applications/cc-applications-panel.css");
+
+ gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+ provider,
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ g_signal_connect_object (self->sidebar_listbox, "row-activated",
+ G_CALLBACK (row_activated_cb), self, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->header_button, "clicked", G_CALLBACK (open_software_cb), self, G_CONNECT_SWAPPED);
+
+ gtk_list_box_set_header_func (self->permission_list,
+ cc_list_box_update_header_func,
+ NULL, NULL);
+
+ gtk_list_box_set_header_func (self->integration_list,
+ cc_list_box_update_header_func,
+ NULL, NULL);
+
+ gtk_list_box_set_header_func (self->handler_list,
+ cc_list_box_update_header_func,
+ NULL, NULL);
+
+ gtk_list_box_set_header_func (self->usage_list,
+ cc_list_box_update_header_func,
+ NULL, NULL);
+
+ gtk_list_box_set_header_func (self->builtin_list,
+ cc_list_box_update_header_func,
+ NULL, NULL);
+
+ gtk_list_box_set_header_func (self->storage_list,
+ cc_list_box_update_header_func,
+ NULL, NULL);
+
+ gtk_list_box_set_sort_func (self->sidebar_listbox,
+ compare_rows,
+ NULL, NULL);
+
+ gtk_list_box_set_filter_func (self->sidebar_listbox,
+ filter_sidebar_rows,
+ self, 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");
+#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 user’s 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_GET_APP_FILTER_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 ();
+
+ /* Select the first row */
+ row = gtk_list_box_get_row_at_index (self->sidebar_listbox, 0);
+ gtk_list_box_select_row (self->sidebar_listbox, row);
+ g_signal_emit_by_name (row, "activate");
+}
diff --git a/panels/applications/cc-applications-panel.css b/panels/applications/cc-applications-panel.css
new file mode 100644
index 0000000..538a9c9
--- /dev/null
+++ b/panels/applications/cc-applications-panel.css
@@ -0,0 +1,11 @@
+.section-title {
+ font-weight: bold;
+}
+
+.section-subtitle {
+ opacity: 0.55;
+}
+
+.sidebar-icon.fullcolor {
+ opacity: 1;
+}
diff --git a/panels/applications/cc-applications-panel.h b/panels/applications/cc-applications-panel.h
new file mode 100644
index 0000000..c6c8882
--- /dev/null
+++ b/panels/applications/cc-applications-panel.h
@@ -0,0 +1,30 @@
+/* cc-applications-panel.h
+ *
+ * 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
+ */
+
+#pragma once
+
+#include <shell/cc-panel.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_APPLICATIONS_PANEL (cc_applications_panel_get_type())
+G_DECLARE_FINAL_TYPE (CcApplicationsPanel, cc_applications_panel, CC, APPLICATIONS_PANEL, CcPanel)
+
+G_END_DECLS
diff --git a/panels/applications/cc-applications-panel.ui b/panels/applications/cc-applications-panel.ui
new file mode 100644
index 0000000..7a08465
--- /dev/null
+++ b/panels/applications/cc-applications-panel.ui
@@ -0,0 +1,608 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcApplicationsPanel" parent="CcPanel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkScrolledWindow" id="main_scroll">
+ <property name="visible">1</property>
+ <property name="hscrollbar-policy">never</property>
+ <child>
+ <object class="HdyClamp">
+ <property name="visible">True</property>
+ <property name="margin_top">32</property>
+ <property name="margin_bottom">32</property>
+ <property name="margin_start">12</property>
+ <property name="margin_end">12</property>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkBox" id="empty_box">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="valign">start</property>
+ <property name="pixel-size">80</property>
+ <property name="icon-name">org.gnome.Software-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="fill">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="margin-bottom">15</property>
+ <property name="label" translatable="yes">No applications</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <attributes>
+ <attribute name="scale" value="1.2"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="label" translatable="yes">Install some…</property>
+ <property name="visible">1</property>
+ <property name="can-focus">1</property>
+ <property name="receives-default">1</property>
+ <property name="halign">center</property>
+ <signal name="clicked" handler="open_software_cb" object="CcApplicationsPanel" swapped="yes"/>
+ </object>
+ <packing>
+ <property name="fill">0</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="settings_box">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">24</property>
+ <property name="hexpand">1</property>
+ <child>
+ <object class="GtkBox" id="permission_section">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <style>
+ <class name="section"/>
+ </style>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Permissions &amp; Access</property>
+ <style>
+ <class name="section-title"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="wrap">1</property>
+ <property name="max-width-chars">50</property>
+ <property name="label" translatable="yes">Data and services that this app has asked for access to and permissions that it requires.</property>
+ <style>
+ <class name="section-subtitle"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="permission_list">
+ <property name="visible">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="permission_row_activated_cb" object="CcApplicationsPanel" swapped="yes"/>
+ <child>
+ <object class="CcToggleRow" id="camera">
+ <property name="title" translatable="yes">Camera</property>
+ <signal name="notify::allowed" handler="camera_cb" object="CcApplicationsPanel" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="CcInfoRow" id="no_camera">
+ <property name="title" translatable="yes">Camera</property>
+ <property name="info" translatable="yes">Disabled</property>
+ </object>
+ </child>
+ <child>
+ <object class="CcToggleRow" id="microphone">
+ <property name="title" translatable="yes">Microphone</property>
+ <signal name="notify::allowed" handler="microphone_cb" object="CcApplicationsPanel" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="CcInfoRow" id="no_microphone">
+ <property name="title" translatable="yes">Microphone</property>
+ <property name="info" translatable="yes">Disabled</property>
+ </object>
+ </child>
+ <child>
+ <object class="CcToggleRow" id="location">
+ <property name="title" translatable="yes">Location Services</property>
+ <signal name="notify::allowed" handler="location_cb" object="CcApplicationsPanel" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="CcInfoRow" id="no_location">
+ <property name="title" translatable="yes">Location Services</property>
+ <property name="info" translatable="yes">Disabled</property>
+ </object>
+ </child>
+ <child>
+ <object class="CcInfoRow" id="builtin">
+ <property name="title" translatable="yes">Built-in Permissions</property>
+ <property name="info" translatable="yes">Cannot be changed</property>
+ <property name="has-expander">True</property>
+ <property name="is-link">True</property>
+ </object>
+ </child>
+ <style>
+ <class name="view"/>
+ <class name="frame"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="wrap">1</property>
+ <property name="max-width-chars">50</property>
+ <property name="label" translatable="yes">Individual permissions for applications can be reviewed in the &lt;a href=&quot;privacy&quot;&gt;Privacy&lt;/a&gt; Settings.</property>
+ <property name="use-markup">1</property>
+ <signal name="activate-link" handler="privacy_link_cb" object="CcApplicationsPanel" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="integration_section">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <style>
+ <class name="section"/>
+ </style>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Integration</property>
+ <style>
+ <class name="section-title"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="wrap">1</property>
+ <property name="max-width-chars">50</property>
+ <property name="label" translatable="yes">System features used by this application.</property>
+ <style>
+ <class name="section-subtitle"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="integration_list">
+ <property name="visible">1</property>
+ <property name="selection-mode">none</property>
+ <child>
+ <object class="CcToggleRow" id="search">
+ <property name="title" translatable="yes">Search</property>
+ <signal name="notify::allowed" handler="search_cb" object="CcApplicationsPanel" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="CcInfoRow" id="no_search">
+ <property name="title" translatable="yes">Search</property>
+ <property name="info" translatable="yes">Disabled</property>
+ </object>
+ </child>
+ <child>
+ <object class="CcToggleRow" id="notification">
+ <property name="title" translatable="yes">Notifications</property>
+ <signal name="notify::allowed" handler="notification_cb" object="CcApplicationsPanel" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="CcToggleRow" id="background">
+ <property name="title" translatable="yes">Run in background</property>
+ <signal name="notify::allowed" handler="background_cb" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="CcToggleRow" id="wallpaper">
+ <property name="title" translatable="yes">Set Desktop Background</property>
+ <signal name="notify::allowed" handler="wallpaper_cb" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="CcToggleRow" id="sound">
+ <property name="title" translatable="yes">Sounds</property>
+ <signal name="notify::allowed" handler="sound_cb" object="CcApplicationsPanel" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="CcInfoRow" id="no_sound">
+ <property name="title" translatable="yes">Sounds</property>
+ <property name="info" translatable="yes">Disabled</property>
+ </object>
+ </child>
+ <child>
+ <object class="CcToggleRow" id="shortcuts">
+ <property name="title" translatable="yes">Inhibit system keyboard shortcuts</property>
+ <signal name="notify::allowed" handler="shortcuts_cb" swapped="yes"/>
+ </object>
+ </child>
+ <style>
+ <class name="view"/>
+ <class name="frame"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="handler_section">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <style>
+ <class name="section"/>
+ </style>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Default Handlers</property>
+ <style>
+ <class name="section-title"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="wrap">1</property>
+ <property name="max-width-chars">50</property>
+ <property name="label" translatable="yes">Types of files and links that this application opens.</property>
+ <style>
+ <class name="section-subtitle"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="handler_reset">
+ <property name="visible">1</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes">Reset</property>
+ <signal name="clicked" handler="handler_reset_cb" object="CcApplicationsPanel" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="handler_list">
+ <property name="visible">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="handler_row_activated_cb" object="CcApplicationsPanel" swapped="yes"/>
+ <style>
+ <class name="view"/>
+ <class name="frame"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="usage_section">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <style>
+ <class name="section"/>
+ </style>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Usage</property>
+ <style>
+ <class name="section-title"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="wrap">1</property>
+ <property name="max-width-chars">50</property>
+ <property name="label" translatable="yes">How much resources this application is using.</property>
+ <style>
+ <class name="section-subtitle"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="usage_list">
+ <property name="visible">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="storage_row_activated_cb" object="CcApplicationsPanel" swapped="yes"/>
+ <child>
+ <object class="CcInfoRow" id="storage">
+ <property name="title" translatable="yes">Storage</property>
+ <property name="info">unknown</property>
+ <property name="has-expander">1</property>
+ <property name="is-link">1</property>
+ </object>
+ </child>
+ <style>
+ <class name="view"/>
+ <class name="frame"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkLabel" id="title_label">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Applications</property>
+ <style>
+ <class name="title"/>
+ </style>
+ </object>
+ <object class="GtkButton" id="header_button">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Open in Software</property>
+ </object>
+
+ <!-- Sidebar -->
+ <object class="GtkBox" id="sidebar_box">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkSearchEntry" id="sidebar_search_entry">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="has-focus">True</property>
+ <property name="margin">12</property>
+ <property name="margin-bottom">6</property>
+ <signal name="activate" handler="on_sidebar_search_entry_activated_cb" object="CcApplicationsPanel" swapped="yes" />
+ <signal name="search-changed" handler="on_sidebar_search_entry_search_changed_cb" object="CcApplicationsPanel" swapped="yes" />
+ <signal name="stop-search" handler="on_sidebar_search_entry_search_stopped_cb" object="CcApplicationsPanel" swapped="yes" />
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="sidebar_listbox">
+ <property name="visible">True</property>
+ <property name="vexpand">True</property>
+ <property name="selection-mode">browse</property>
+
+ <child type="placeholder">
+ <object class="GtkBox" id="empty_search_placeholder">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="expand">True</property>
+ <property name="border_width">18</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pixel_size">64</property>
+ <property name="icon_name">edit-find-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">No results found</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.44"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Try a different search</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </object>
+
+ <!-- Built-in Permissions dialog -->
+ <object class="GtkDialog" id="builtin_dialog">
+ <property name="title" translatable="yes">Built-in Permissions</property>
+ <property name="modal">1</property>
+ <property name="type-hint">dialog</property>
+ <property name="use-header-bar">1</property>
+ <property name="resizable">0</property>
+ <property name="border-width">24</property>
+ <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="builtin_label">
+ <property name="visible">1</property>
+ <property name="wrap">1</property>
+ <property name="max-width-chars">50</property>
+ <property name="label">Yadda Yadda</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="builtin_list">
+ <property name="visible">1</property>
+ <property name="selection-mode">none</property>
+ <style>
+ <class name="view"/>
+ <class name="frame"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+
+ <!-- Storage dialog -->
+ <object class="GtkDialog" id="storage_dialog">
+ <property name="title" translatable="yes">Storage</property>
+ <property name="modal">1</property>
+ <property name="type-hint">dialog</property>
+ <property name="use-header-bar">1</property>
+ <property name="resizable">0</property>
+ <property name="border-width">24</property>
+ <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="wrap">1</property>
+ <property name="max-width-chars">50</property>
+ <property name="label" translatable="yes">How much disk space this application is occupying with app data and caches.</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="storage_list">
+ <property name="visible">1</property>
+ <property name="selection-mode">none</property>
+ <child>
+ <object class="CcInfoRow" id="app">
+ <property name="title" translatable="yes">Application</property>
+ <property name="info">Unknown</property>
+ </object>
+ </child>
+ <child>
+ <object class="CcInfoRow" id="data">
+ <property name="title" translatable="yes">Data</property>
+ <property name="info">Unknown</property>
+ </object>
+ </child>
+ <child>
+ <object class="CcInfoRow" id="cache">
+ <property name="title" translatable="yes">Cache</property>
+ <property name="info">Unknown</property>
+ </object>
+ </child>
+ <child>
+ <object class="CcInfoRow" id="total">
+ <property name="title" translatable="yes">&lt;b&gt;Total&lt;/b&gt;</property>
+ <property name="use-markup">1</property>
+ <property name="info">Unknown</property>
+ </object>
+ </child>
+ <style>
+ <class name="view"/>
+ <class name="frame"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkButton" id="clear_cache_button">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Clear Cache…</property>
+ <signal name="clicked" handler="clear_cache_cb" object="CcApplicationsPanel" swapped="yes"/>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/panels/applications/cc-applications-row.c b/panels/applications/cc-applications-row.c
new file mode 100644
index 0000000..08516ac
--- /dev/null
+++ b/panels/applications/cc-applications-row.c
@@ -0,0 +1,108 @@
+/* cc-applications-row.c
+ *
+ * Copyright 2018 Matthias Clasen <matthias.clasen@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
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include "cc-applications-row.h"
+#include "cc-applications-resources.h"
+
+struct _CcApplicationsRow
+{
+ GtkListBoxRow parent;
+
+ GAppInfo *info;
+ gchar *sortkey;
+
+ GtkWidget *box;
+ GtkWidget *image;
+ GtkWidget *label;
+};
+
+G_DEFINE_TYPE (CcApplicationsRow, cc_applications_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_applications_row_finalize (GObject *object)
+{
+ CcApplicationsRow *self = CC_APPLICATIONS_ROW (object);
+
+ g_object_unref (self->info);
+ g_free (self->sortkey);
+
+ G_OBJECT_CLASS (cc_applications_row_parent_class)->finalize (object);
+}
+
+static void
+cc_applications_row_class_init (CcApplicationsRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = cc_applications_row_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-applications-row.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsRow, box);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsRow, image);
+ gtk_widget_class_bind_template_child (widget_class, CcApplicationsRow, label);
+}
+
+static void
+cc_applications_row_init (CcApplicationsRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcApplicationsRow *
+cc_applications_row_new (GAppInfo *info)
+{
+ CcApplicationsRow *self;
+ g_autofree gchar *key = NULL;
+ GIcon *icon;
+
+ self = g_object_new (CC_TYPE_APPLICATIONS_ROW, NULL);
+
+ self->info = g_object_ref (info);
+
+ key = g_utf8_casefold (g_app_info_get_display_name (info), -1);
+ self->sortkey = g_utf8_collate_key (key, -1);
+
+ icon = g_app_info_get_icon (info);
+ if (icon != NULL)
+ gtk_image_set_from_gicon (GTK_IMAGE (self->image), g_app_info_get_icon (info), GTK_ICON_SIZE_BUTTON);
+ else
+ gtk_image_set_from_icon_name (GTK_IMAGE (self->image), "application-x-executable", GTK_ICON_SIZE_BUTTON);
+
+ gtk_label_set_label (GTK_LABEL (self->label), g_app_info_get_display_name (info));
+
+ return self;
+}
+
+GAppInfo *
+cc_applications_row_get_info (CcApplicationsRow *self)
+{
+ return self->info;
+}
+
+const gchar *
+cc_applications_row_get_sort_key (CcApplicationsRow *self)
+{
+ return self->sortkey;
+}
diff --git a/panels/applications/cc-applications-row.h b/panels/applications/cc-applications-row.h
new file mode 100644
index 0000000..7ca44e1
--- /dev/null
+++ b/panels/applications/cc-applications-row.h
@@ -0,0 +1,36 @@
+/* cc-applications-row.h
+ *
+ * Copyright 2018 Matthias Clasen <matthias.clasen@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
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_APPLICATIONS_ROW (cc_applications_row_get_type())
+G_DECLARE_FINAL_TYPE (CcApplicationsRow, cc_applications_row, CC, APPLICATIONS_ROW, GtkListBoxRow)
+
+CcApplicationsRow* cc_applications_row_new (GAppInfo *info);
+
+GAppInfo* cc_applications_row_get_info (CcApplicationsRow *row);
+
+const gchar* cc_applications_row_get_sort_key (CcApplicationsRow *row);
+
+G_END_DECLS
diff --git a/panels/applications/cc-applications-row.ui b/panels/applications/cc-applications-row.ui
new file mode 100644
index 0000000..819e48a
--- /dev/null
+++ b/panels/applications/cc-applications-row.ui
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcApplicationsRow" parent="GtkListBoxRow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="visible">1</property>
+ <property name="border-width">6</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkImage" id="image">
+ <property name="visible">1</property>
+ <property name="pixel-size">32</property>
+ <style>
+ <class name="sidebar-icon"/>
+ <class name="fullcolor"/>
+ <class name="lowres-icon"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="ellipsize">end</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/applications/cc-info-row.c b/panels/applications/cc-info-row.c
new file mode 100644
index 0000000..3a8d88a
--- /dev/null
+++ b/panels/applications/cc-info-row.c
@@ -0,0 +1,215 @@
+/* cc-info-row.c
+ *
+ * Copyright 2018 Matthias Clasen <matthias.clasen@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
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include "cc-info-row.h"
+#include "cc-applications-resources.h"
+
+struct _CcInfoRow
+{
+ GtkListBoxRow parent;
+
+ GtkWidget *title;
+ GtkWidget *info;
+ GtkWidget *expander;
+
+ gboolean expanded;
+ gboolean link;
+};
+
+G_DEFINE_TYPE (CcInfoRow, cc_info_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum
+{
+ PROP_0,
+ PROP_TITLE,
+ PROP_USE_MARKUP,
+ PROP_INFO,
+ PROP_HAS_EXPANDER,
+ PROP_IS_LINK,
+ PROP_EXPANDED
+};
+
+static void
+cc_info_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcInfoRow *row = CC_INFO_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_TITLE:
+ g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->title)));
+ break;
+ case PROP_INFO:
+ g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->info)));
+ break;
+ case PROP_HAS_EXPANDER:
+ g_value_set_boolean (value, gtk_widget_get_visible (row->expander));
+ break;
+ case PROP_USE_MARKUP:
+ g_value_set_boolean (value, gtk_label_get_use_markup (GTK_LABEL (row->title)));
+ break;
+ case PROP_IS_LINK:
+ g_value_set_boolean (value, row->link);
+ break;
+ case PROP_EXPANDED:
+ g_value_set_boolean (value, cc_info_row_get_expanded (row));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+update_expander (CcInfoRow *row)
+{
+ if (row->link)
+ gtk_image_set_from_icon_name (GTK_IMAGE (row->expander), "go-next-symbolic", GTK_ICON_SIZE_BUTTON);
+ else if (row->expanded)
+ gtk_image_set_from_icon_name (GTK_IMAGE (row->expander), "pan-down-symbolic", GTK_ICON_SIZE_BUTTON);
+ else
+ gtk_image_set_from_icon_name (GTK_IMAGE (row->expander), "pan-end-symbolic", GTK_ICON_SIZE_BUTTON);
+}
+
+static void
+cc_info_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcInfoRow *row = CC_INFO_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_TITLE:
+ gtk_label_set_label (GTK_LABEL (row->title), g_value_get_string (value));
+ break;
+
+ case PROP_INFO:
+ gtk_label_set_label (GTK_LABEL (row->info), g_value_get_string (value));
+ break;
+
+ case PROP_HAS_EXPANDER:
+ gtk_widget_set_visible (row->expander, g_value_get_boolean (value));
+ gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), g_value_get_boolean (value));
+ break;
+
+ case PROP_USE_MARKUP:
+ gtk_label_set_use_markup (GTK_LABEL (row->title), g_value_get_boolean (value));
+ break;
+
+ case PROP_IS_LINK:
+ row->link = g_value_get_boolean (value);
+ update_expander (row);
+ break;
+
+ case PROP_EXPANDED:
+ cc_info_row_set_expanded (row, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cc_info_row_class_init (CcInfoRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = cc_info_row_get_property;
+ object_class->set_property = cc_info_row_set_property;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-info-row.ui");
+
+ g_object_class_install_property (object_class,
+ PROP_TITLE,
+ g_param_spec_string ("title", "title", "title",
+ NULL, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_INFO,
+ g_param_spec_string ("info", "info", "info",
+ NULL, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_USE_MARKUP,
+ g_param_spec_boolean ("use-markup", "use-markup", "use-markup",
+ FALSE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_HAS_EXPANDER,
+ g_param_spec_boolean ("has-expander", "has-expander", "has-expander",
+ FALSE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_EXPANDED,
+ g_param_spec_boolean ("expanded", "expanded", "expanded",
+ FALSE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_IS_LINK,
+ g_param_spec_boolean ("is-link", "is-link", "is-link",
+ FALSE, G_PARAM_READWRITE));
+
+ gtk_widget_class_bind_template_child (widget_class, CcInfoRow, title);
+ gtk_widget_class_bind_template_child (widget_class, CcInfoRow, info);
+ gtk_widget_class_bind_template_child (widget_class, CcInfoRow, expander);
+}
+
+static void
+cc_info_row_init (CcInfoRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcInfoRow *
+cc_info_row_new (void)
+{
+ return CC_INFO_ROW (g_object_new (CC_TYPE_INFO_ROW, NULL));
+}
+
+gboolean
+cc_info_row_get_expanded (CcInfoRow *row)
+{
+ return row->expanded;
+}
+
+void
+cc_info_row_set_expanded (CcInfoRow *row,
+ gboolean expanded)
+{
+ if (row->expanded == expanded)
+ return;
+
+ row->expanded = expanded;
+ update_expander (row);
+
+ g_object_notify (G_OBJECT (row), "expanded");
+}
+
diff --git a/panels/applications/cc-info-row.h b/panels/applications/cc-info-row.h
new file mode 100644
index 0000000..57b9d4a
--- /dev/null
+++ b/panels/applications/cc-info-row.h
@@ -0,0 +1,37 @@
+/* cc-info-row.h
+ *
+ * Copyright 2018 Matthias Clasen <matthias.clasen@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
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_INFO_ROW (cc_info_row_get_type())
+G_DECLARE_FINAL_TYPE (CcInfoRow, cc_info_row, CC, INFO_ROW, GtkListBoxRow)
+
+CcInfoRow* cc_info_row_new (void);
+
+void cc_info_row_set_expanded (CcInfoRow *row,
+ gboolean expanded);
+
+gboolean cc_info_row_get_expanded (CcInfoRow *row);
+
+G_END_DECLS
diff --git a/panels/applications/cc-info-row.ui b/panels/applications/cc-info-row.ui
new file mode 100644
index 0000000..dcc7a42
--- /dev/null
+++ b/panels/applications/cc-info-row.ui
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcInfoRow" parent="GtkListBoxRow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="activatable">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="border-width">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="title">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="hexpand">1</property>
+ <property name="ellipsize">end</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="info">
+ <property name="visible">1</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="expander">
+ <property name="valign">center</property>
+ <property name="icon-name">pan-end-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/applications/cc-snap-row.c b/panels/applications/cc-snap-row.c
new file mode 100644
index 0000000..40a7fc4
--- /dev/null
+++ b/panels/applications/cc-snap-row.c
@@ -0,0 +1,316 @@
+/* cc-snap-row.c
+ *
+ * Copyright 2019 Canonical Ltd.
+ *
+ * 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
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include "cc-snap-row.h"
+#include "cc-applications-resources.h"
+
+struct _CcSnapRow
+{
+ GtkListBoxRow parent;
+
+ GtkLabel *title_label;
+ GtkSwitch *slot_toggle;
+ GtkComboBox *slots_combo;
+ GtkListStore *slots_combo_model;
+
+ GCancellable *cancellable;
+
+ SnapdPlug *plug;
+ SnapdSlot *connected_slot;
+ GPtrArray *slots;
+};
+
+G_DEFINE_TYPE (CcSnapRow, cc_snap_row, GTK_TYPE_LIST_BOX_ROW)
+
+typedef struct
+{
+ CcSnapRow *self;
+ SnapdSlot *slot;
+} ConnectData;
+
+static void
+update_state (CcSnapRow *self)
+{
+ gboolean have_single_option;
+ GtkTreeIter iter;
+
+ have_single_option = self->slots->len == 1;
+ gtk_widget_set_visible (GTK_WIDGET (self->slot_toggle), have_single_option);
+ gtk_widget_set_visible (GTK_WIDGET (self->slots_combo), !have_single_option);
+
+ gtk_switch_set_active (self->slot_toggle, self->connected_slot != NULL);
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->slots_combo_model), &iter))
+ {
+ do
+ {
+ SnapdSlot *slot;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (self->slots_combo_model), &iter, 0, &slot, -1);
+ if (slot == self->connected_slot)
+ gtk_combo_box_set_active_iter (self->slots_combo, &iter);
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->slots_combo_model), &iter));
+ }
+}
+
+static void
+disable_controls (CcSnapRow *self)
+{
+ gtk_widget_set_sensitive (GTK_WIDGET (self->slot_toggle), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->slots_combo), FALSE);
+}
+
+static void
+enable_controls (CcSnapRow *self)
+{
+ gtk_widget_set_sensitive (GTK_WIDGET (self->slot_toggle), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->slots_combo), TRUE);
+}
+
+static ConnectData *
+connect_data_new (CcSnapRow *self, SnapdSlot *slot)
+{
+ ConnectData *data;
+
+ data = g_new0 (ConnectData, 1);
+ data->self = self;
+ data->slot = g_object_ref (slot);
+
+ return data;
+}
+
+static void
+connect_data_free (ConnectData *data)
+{
+ g_clear_object (&data->slot);
+ g_free (data);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (ConnectData, connect_data_free)
+
+static void
+connect_interface_cb (GObject *client, GAsyncResult *result, gpointer user_data)
+{
+ g_autoptr(ConnectData) data = user_data;
+ CcSnapRow *self = data->self;
+ SnapdSlot *slot = data->slot;
+ g_autoptr(GError) error = NULL;
+
+ if (snapd_client_connect_interface_finish (SNAPD_CLIENT (client), result, &error))
+ {
+ g_clear_object (&self->connected_slot);
+ self->connected_slot = g_object_ref (slot);
+ }
+ else
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+ g_warning ("Failed to connect interface: %s", error->message);
+ }
+
+ update_state (self);
+ enable_controls (self);
+}
+
+static void
+connect_plug (CcSnapRow *self, SnapdSlot *slot)
+{
+ g_autoptr(SnapdClient) client = NULL;
+
+ /* already connected */
+ if (self->connected_slot == slot)
+ return;
+
+ disable_controls (self);
+
+ client = snapd_client_new ();
+ snapd_client_connect_interface_async (client,
+ snapd_plug_get_snap (self->plug), snapd_plug_get_name (self->plug),
+ snapd_slot_get_snap (slot), snapd_slot_get_name (slot),
+ NULL, NULL,
+ self->cancellable,
+ connect_interface_cb, connect_data_new (self, slot));
+}
+
+static void
+disconnect_interface_cb (GObject *client, GAsyncResult *result, gpointer user_data)
+{
+ CcSnapRow *self = user_data;
+ g_autoptr(GError) error = NULL;
+
+ if (snapd_client_disconnect_interface_finish (SNAPD_CLIENT (client), result, &error))
+ {
+ g_clear_object (&self->connected_slot);
+ }
+ else
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+ g_warning ("Failed to disconnect interface: %s", error->message);
+ }
+
+ update_state (self);
+ enable_controls (self);
+}
+
+static void
+disconnect_plug (CcSnapRow *self)
+{
+ g_autoptr(SnapdClient) client = NULL;
+
+ /* already disconnected */
+ if (self->connected_slot == NULL)
+ return;
+
+ disable_controls (self);
+
+ client = snapd_client_new ();
+ snapd_client_disconnect_interface_async (client,
+ snapd_plug_get_snap (self->plug), snapd_plug_get_name (self->plug),
+ NULL, NULL,
+ NULL, NULL,
+ self->cancellable,
+ disconnect_interface_cb, self);
+}
+
+static void
+switch_changed_cb (CcSnapRow *self)
+{
+ if (gtk_switch_get_active (self->slot_toggle))
+ {
+ if (self->slots->len == 1)
+ connect_plug (self, g_ptr_array_index (self->slots, 0));
+ }
+ else
+ {
+ disconnect_plug (self);
+ }
+}
+
+static void
+combo_changed_cb (CcSnapRow *self)
+{
+ GtkTreeIter iter;
+ SnapdSlot *slot = NULL;
+
+ if (!gtk_combo_box_get_active_iter (self->slots_combo, &iter))
+ return;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (self->slots_combo_model), &iter, 0, &slot, -1);
+ if (slot != NULL)
+ connect_plug (self, slot);
+ else
+ disconnect_plug (self);
+}
+
+static void
+cc_snap_row_finalize (GObject *object)
+{
+ CcSnapRow *self = CC_SNAP_ROW (object);
+
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->plug);
+ g_clear_pointer (&self->slots, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (cc_snap_row_parent_class)->finalize (object);
+}
+
+static void
+cc_snap_row_class_init (CcSnapRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = cc_snap_row_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-snap-row.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcSnapRow, title_label);
+ gtk_widget_class_bind_template_child (widget_class, CcSnapRow, slot_toggle);
+ gtk_widget_class_bind_template_child (widget_class, CcSnapRow, slots_combo);
+ gtk_widget_class_bind_template_child (widget_class, CcSnapRow, slots_combo_model);
+
+ gtk_widget_class_bind_template_callback (widget_class, combo_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, switch_changed_cb);
+}
+
+static void
+cc_snap_row_init (CcSnapRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcSnapRow *
+cc_snap_row_new (GCancellable *cancellable, SnapdInterface *interface, SnapdPlug *plug, GPtrArray *slots)
+{
+ CcSnapRow *self;
+ GPtrArray *connected_slots;
+ g_autofree gchar *label = NULL;
+ GtkTreeIter iter;
+
+ g_return_val_if_fail (SNAPD_IS_PLUG (plug), NULL);
+
+ self = CC_SNAP_ROW (g_object_new (CC_TYPE_SNAP_ROW, NULL));
+
+ self->cancellable = g_object_ref (cancellable);
+ self->plug = g_object_ref (plug);
+ self->slots = g_ptr_array_ref (slots);
+
+ connected_slots = snapd_plug_get_connected_slots (plug);
+ if (connected_slots->len > 0)
+ {
+ SnapdSlotRef *connected_slot_ref = g_ptr_array_index (connected_slots, 0);
+
+ for (int i = 0; i < slots->len; i++)
+ {
+ SnapdSlot *slot = g_ptr_array_index (slots, i);
+
+ if (g_strcmp0 (snapd_slot_get_snap (slot), snapd_slot_ref_get_snap (connected_slot_ref)) == 0 &&
+ g_strcmp0 (snapd_slot_get_name (slot), snapd_slot_ref_get_slot (connected_slot_ref)) == 0)
+ self->connected_slot = slot;
+ }
+ }
+
+ if (interface != NULL)
+ label = snapd_interface_make_label (interface);
+ else
+ label = g_strdup (snapd_plug_get_interface (plug));
+ gtk_label_set_label (self->title_label, label);
+
+ /* Add option into combo box */
+ gtk_list_store_append (self->slots_combo_model, &iter);
+ gtk_list_store_set (self->slots_combo_model, &iter, 1, "--", -1);
+ for (int i = 0; i < slots->len; i++)
+ {
+ SnapdSlot *slot = g_ptr_array_index (slots, i);
+ g_autofree gchar *label = NULL;
+
+ label = g_strdup_printf ("%s:%s", snapd_slot_get_snap (slot), snapd_slot_get_name (slot));
+ gtk_list_store_append (self->slots_combo_model, &iter);
+ gtk_list_store_set (self->slots_combo_model, &iter, 0, slot, 1, label, -1);
+ }
+
+ update_state (self);
+
+ return self;
+}
diff --git a/panels/applications/cc-snap-row.h b/panels/applications/cc-snap-row.h
new file mode 100644
index 0000000..d37bb80
--- /dev/null
+++ b/panels/applications/cc-snap-row.h
@@ -0,0 +1,36 @@
+/* cc-snap-row.h
+ *
+ * Copyright 2019 Canonical Ltd.
+ *
+ * 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
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <snapd-glib/snapd-glib.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_SNAP_ROW (cc_snap_row_get_type())
+G_DECLARE_FINAL_TYPE (CcSnapRow, cc_snap_row, CC, SNAP_ROW, GtkListBoxRow)
+
+CcSnapRow* cc_snap_row_new (GCancellable *cancellable,
+ SnapdInterface *interface,
+ SnapdPlug *plug,
+ GPtrArray *slots);
+
+G_END_DECLS
diff --git a/panels/applications/cc-snap-row.ui b/panels/applications/cc-snap-row.ui
new file mode 100644
index 0000000..2d83f37
--- /dev/null
+++ b/panels/applications/cc-snap-row.ui
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <object class="GtkListStore" id="slots_combo_model">
+ <columns>
+ <!-- column-name slot -->
+ <column type="GObject"/>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <template class="CcSnapRow" parent="GtkListBoxRow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="activatable">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="border-width">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="title_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="hexpand">1</property>
+ <property name="ellipsize">end</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="slot_toggle">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ <signal name="notify::active" handler="switch_changed_cb" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="slots_combo">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ <property name="model">slots_combo_model</property>
+ <signal name="changed" handler="combo_changed_cb" swapped="yes"/>
+ <child>
+ <object class="GtkCellRendererText">
+ <property name="ellipsize">end</property>
+ </object>
+ <attributes>
+ <attribute name="text">1</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/applications/cc-toggle-row.c b/panels/applications/cc-toggle-row.c
new file mode 100644
index 0000000..4895cc4
--- /dev/null
+++ b/panels/applications/cc-toggle-row.c
@@ -0,0 +1,144 @@
+/* cc-toggle-row.c
+ *
+ * Copyright 2018 Matthias Clasen <matthias.clasen@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
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include "cc-toggle-row.h"
+#include "cc-applications-resources.h"
+
+struct _CcToggleRow
+{
+ GtkListBoxRow parent;
+
+ GtkWidget *title;
+ GtkWidget *toggle;
+};
+
+G_DEFINE_TYPE (CcToggleRow, cc_toggle_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum
+{
+ PROP_0,
+ PROP_TITLE,
+ PROP_ALLOWED
+};
+
+static void
+changed_cb (CcToggleRow *row)
+{
+ g_object_notify (G_OBJECT (row), "allowed");
+}
+
+static void
+cc_toggle_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcToggleRow *row = CC_TOGGLE_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_TITLE:
+ g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->title)));
+ break;
+ case PROP_ALLOWED:
+ g_value_set_boolean (value, cc_toggle_row_get_allowed (row));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cc_toggle_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcToggleRow *row = CC_TOGGLE_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_TITLE:
+ gtk_label_set_label (GTK_LABEL (row->title), g_value_get_string (value));
+ break;
+ case PROP_ALLOWED:
+ cc_toggle_row_set_allowed (row, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cc_toggle_row_class_init (CcToggleRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = cc_toggle_row_get_property;
+ object_class->set_property = cc_toggle_row_set_property;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-toggle-row.ui");
+
+ g_object_class_install_property (object_class,
+ PROP_TITLE,
+ g_param_spec_string ("title", "title", "title",
+ NULL, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_ALLOWED,
+ g_param_spec_boolean ("allowed", "allowed", "allowed",
+ FALSE, G_PARAM_READWRITE));
+
+ gtk_widget_class_bind_template_child (widget_class, CcToggleRow, title);
+ gtk_widget_class_bind_template_child (widget_class, CcToggleRow, toggle);
+
+ gtk_widget_class_bind_template_callback (widget_class, changed_cb);
+}
+
+static void
+cc_toggle_row_init (CcToggleRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcToggleRow *
+cc_toggle_row_new (void)
+{
+ return CC_TOGGLE_ROW (g_object_new (CC_TYPE_TOGGLE_ROW, NULL));
+}
+
+void
+cc_toggle_row_set_allowed (CcToggleRow *self,
+ gboolean allowed)
+{
+ gtk_switch_set_active (GTK_SWITCH (self->toggle), allowed);
+}
+
+gboolean
+cc_toggle_row_get_allowed (CcToggleRow *self)
+{
+ return gtk_switch_get_active (GTK_SWITCH (self->toggle));
+}
diff --git a/panels/applications/cc-toggle-row.h b/panels/applications/cc-toggle-row.h
new file mode 100644
index 0000000..cfc66b9
--- /dev/null
+++ b/panels/applications/cc-toggle-row.h
@@ -0,0 +1,37 @@
+/* cc-toggle-row.h
+ *
+ * Copyright 2018 Matthias Clasen <matthias.clasen@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
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_TOGGLE_ROW (cc_toggle_row_get_type())
+G_DECLARE_FINAL_TYPE (CcToggleRow, cc_toggle_row, CC, TOGGLE_ROW, GtkListBoxRow)
+
+CcToggleRow* cc_toggle_row_new (void);
+
+void cc_toggle_row_set_allowed (CcToggleRow *row,
+ gboolean allowed);
+
+gboolean cc_toggle_row_get_allowed (CcToggleRow *row);
+
+G_END_DECLS
diff --git a/panels/applications/cc-toggle-row.ui b/panels/applications/cc-toggle-row.ui
new file mode 100644
index 0000000..8dea8f0
--- /dev/null
+++ b/panels/applications/cc-toggle-row.ui
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcToggleRow" parent="GtkListBoxRow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="activatable">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="border-width">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="title">
+ <property name="visible">1</property>
+ <property name="xalign">0</property>
+ <property name="hexpand">1</property>
+ <property name="ellipsize">end</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSwitch" id="toggle">
+ <property name="visible">1</property>
+ <property name="valign">center</property>
+ <signal name="notify::active" handler="changed_cb" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/panels/applications/globs.c b/panels/applications/globs.c
new file mode 100644
index 0000000..4d2c939
--- /dev/null
+++ b/panels/applications/globs.c
@@ -0,0 +1,62 @@
+/* globs.c
+ *
+ * Copyright 2018 Matthias Clasen <matthias.clasen@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
+ */
+
+#include <config.h>
+
+#include "globs.h"
+
+/* parse mime/globs and return a string->string hash table */
+GHashTable *
+parse_globs (void)
+{
+ GHashTable *globs;
+ const gchar * const *dirs;
+ gint i;
+
+ globs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ dirs = g_get_system_data_dirs ();
+
+ for (i = 0; dirs[i]; i++)
+ {
+ g_autofree gchar *file = g_build_filename (dirs[i], "mime", "globs", NULL);
+ g_autofree gchar *contents = NULL;
+
+ if (g_file_get_contents (file, &contents, NULL, NULL))
+ {
+ g_auto(GStrv) strv = NULL;
+ int i;
+
+ strv = g_strsplit (contents, "\n", 0);
+ for (i = 0; strv[i]; i++)
+ {
+ g_auto(GStrv) parts = NULL;
+
+ if (strv[i][0] == '#' || strv[i][0] == '\0')
+ continue;
+
+ parts = g_strsplit (strv[i], ":", 2);
+ g_hash_table_insert (globs, g_strdup (parts[0]), g_strdup (parts[1]));
+ }
+ }
+ }
+
+ return globs;
+}
diff --git a/panels/applications/globs.h b/panels/applications/globs.h
new file mode 100644
index 0000000..0a54588
--- /dev/null
+++ b/panels/applications/globs.h
@@ -0,0 +1,29 @@
+/* globs.h
+ *
+ * Copyright 2018 Matthias Clasen <matthias.clasen@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
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+GHashTable* parse_globs (void);
+
+G_END_DECLS
diff --git a/panels/applications/gnome-applications-panel.desktop.in.in b/panels/applications/gnome-applications-panel.desktop.in.in
new file mode 100644
index 0000000..86e8166
--- /dev/null
+++ b/panels/applications/gnome-applications-panel.desktop.in.in
@@ -0,0 +1,16 @@
+[Desktop Entry]
+Name=Applications
+Comment=Control various application permissions and settings
+Exec=gnome-control-center applications
+# FIXME
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=preferences-desktop-apps
+Terminal=false
+Type=Application
+NoDisplay=true
+StartupNotify=true
+Categories=GNOME;GTK;Settings;DesktopSettings;X-GNOME-Settings-Panel;X-GNOME-AccountSettings;
+OnlyShowIn=GNOME;Unity;
+# Translators: Search terms to find the Privacy panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
+Keywords=application;flatpak;permission;setting;
+X-GNOME-ControlCenter-HasSidebar=true \ No newline at end of file
diff --git a/panels/applications/meson.build b/panels/applications/meson.build
new file mode 100644
index 0000000..d511bf6
--- /dev/null
+++ b/panels/applications/meson.build
@@ -0,0 +1,58 @@
+panels_list += cappletname
+desktop = 'gnome-@0@-panel.desktop'.format(cappletname)
+
+desktop_in = configure_file(
+ input : desktop + '.in.in',
+ output : desktop + '.in',
+ configuration : desktop_conf
+)
+
+i18n.merge_file(
+ desktop,
+ type : 'desktop',
+ input : desktop_in,
+ output : desktop,
+ po_dir : po_dir,
+ install : true,
+ install_dir : control_center_desktopdir
+)
+
+sources = files(
+ 'cc-applications-panel.c',
+ 'cc-applications-row.c',
+ 'cc-toggle-row.c',
+ 'cc-info-row.c',
+ 'cc-action-row.c',
+ 'globs.c',
+ 'search.c',
+ 'utils.c',
+)
+
+resource_data = files('cc-applications-panel.ui')
+
+sources += gnome.compile_resources(
+ 'cc-' + cappletname + '-resources',
+ cappletname + '.gresource.xml',
+ c_name : 'cc_' + cappletname,
+ dependencies : resource_data,
+ export : true
+)
+
+deps = common_deps
+
+if enable_snap
+ deps += snapd_glib_deps
+ sources += files('cc-snap-row.c')
+endif
+
+if enable_malcontent
+ deps += malcontent_dep
+endif
+
+panels_libs += static_library(
+ cappletname,
+ sources : sources,
+ include_directories : [ top_inc, common_inc ],
+ dependencies : deps,
+ c_args : cflags
+)
diff --git a/panels/applications/search.c b/panels/applications/search.c
new file mode 100644
index 0000000..448918b
--- /dev/null
+++ b/panels/applications/search.c
@@ -0,0 +1,133 @@
+/* search.c
+ *
+ * Copyright 2018 Matthias Clasen <matthias.clasen@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
+ */
+
+#include <config.h>
+
+#include "search.h"
+
+
+#define SHELL_PROVIDER_GROUP "Shell Search Provider"
+
+static void
+add_one_provider (GHashTable *search_providers,
+ GFile *file)
+{
+ g_autoptr(GKeyFile) keyfile = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar *app_id = NULL;
+ g_autofree gchar *path = NULL;
+ gboolean default_disabled;
+
+ path = g_file_get_path (file);
+ keyfile = g_key_file_new ();
+ g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Error loading %s: %s - search provider will be ignored",
+ path, error->message);
+ return;
+ }
+
+ if (!g_key_file_has_group (keyfile, SHELL_PROVIDER_GROUP))
+ {
+ g_debug ("Shell search provider group missing from '%s', ignoring", path);
+ return;
+ }
+
+ app_id = g_key_file_get_string (keyfile, SHELL_PROVIDER_GROUP, "DesktopId", &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Unable to read desktop ID from %s: %s - search provider will be ignored",
+ path, error->message);
+ return;
+ }
+
+ if (g_str_has_suffix (app_id, ".desktop"))
+ app_id[strlen (app_id) - strlen (".desktop")] = '\0';
+
+ default_disabled = g_key_file_get_boolean (keyfile, SHELL_PROVIDER_GROUP, "DefaultDisabled", NULL);
+
+ g_hash_table_insert (search_providers, g_strdup (app_id), GINT_TO_POINTER (default_disabled));
+}
+
+static void
+parse_search_providers_one_dir (GHashTable *search_providers,
+ const gchar *system_dir)
+{
+ g_autoptr(GFileEnumerator) enumerator = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GFile) providers_location = NULL;
+ g_autofree gchar *providers_path = NULL;
+
+ providers_path = g_build_filename (system_dir, "gnome-shell", "search-providers", NULL);
+ providers_location = g_file_new_for_path (providers_path);
+
+ enumerator = g_file_enumerate_children (providers_location,
+ "standard::type,standard::name,standard::content-type",
+ G_FILE_QUERY_INFO_NONE,
+ NULL, &error);
+
+ if (error != NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
+ !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Error opening %s: %s - search provider configuration won't be possible",
+ providers_path, error->message);
+ return;
+ }
+
+ while (TRUE)
+ {
+ GFile *provider = NULL;
+
+ if (!g_file_enumerator_iterate (enumerator, NULL, &provider, NULL, &error))
+ {
+ g_warning ("Error while reading %s: %s - search provider configuration won't be possible",
+ providers_path, error->message);
+ return;
+ }
+
+ if (provider == NULL)
+ break;
+
+ add_one_provider (search_providers, provider);
+ }
+}
+
+/* parse gnome-shell/search-provider files and return a string->boolean hash table */
+GHashTable *
+parse_search_providers (void)
+{
+ GHashTable *search_providers;
+ const gchar * const *dirs;
+ gint i;
+
+ search_providers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ dirs = g_get_system_data_dirs ();
+
+ for (i = 0; dirs[i]; i++)
+ parse_search_providers_one_dir (search_providers, dirs[i]);
+
+ return search_providers;
+}
+
diff --git a/panels/applications/search.h b/panels/applications/search.h
new file mode 100644
index 0000000..b7ade82
--- /dev/null
+++ b/panels/applications/search.h
@@ -0,0 +1,29 @@
+/* globs.h
+ *
+ * Copyright 2018 Matthias Clasen <matthias.clasen@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
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+GHashTable* parse_search_providers (void);
+
+G_END_DECLS
diff --git a/panels/applications/utils.c b/panels/applications/utils.c
new file mode 100644
index 0000000..96704a4
--- /dev/null
+++ b/panels/applications/utils.c
@@ -0,0 +1,268 @@
+/* utils.c
+ *
+ * Copyright 2018 Matthias Clasen <matthias.clasen@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
+ */
+
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE 600
+#endif
+
+#include <config.h>
+#include <glib/gi18n.h>
+#ifdef HAVE_SNAP
+#include <snapd-glib/snapd-glib.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ftw.h>
+
+#include "utils.h"
+
+static gint
+ftw_remove_cb (const gchar *path,
+ const struct stat *sb,
+ gint typeflags,
+ struct FTW *ftwbuf)
+{
+ remove (path);
+ return 0;
+}
+
+static void
+file_remove_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GFile *file = source_object;
+ g_autofree gchar *path = g_file_get_path (file);
+
+ nftw (path, ftw_remove_cb, 20, FTW_DEPTH);
+
+ if (g_task_set_return_on_cancel (task, FALSE))
+ g_task_return_boolean (task, TRUE);
+}
+
+void
+file_remove_async (GFile *file,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer data)
+{
+ g_autoptr(GTask) task = g_task_new (file, cancellable, callback, data);
+ g_task_set_return_on_cancel (task, TRUE);
+ g_task_run_in_thread (task, file_remove_thread_func);
+}
+
+gboolean
+file_remove_finish (GFile *file,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, file), FALSE);
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static GPrivate size_key = G_PRIVATE_INIT (g_free);
+
+static gint
+ftw_size_cb (const gchar *path,
+ const struct stat *sb,
+ gint typeflags,
+ struct FTW *ftwbuf)
+{
+ guint64 *size = (guint64*)g_private_get (&size_key);
+ if (typeflags == FTW_F)
+ *size += sb->st_size;
+ return 0;
+}
+
+static void
+file_size_thread_func (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GFile *file = source_object;
+ g_autofree gchar *path = g_file_get_path (file);
+ guint64 *total;
+
+ g_private_replace (&size_key, g_new0 (guint64, 1));
+
+ nftw (path, ftw_size_cb, 20, FTW_DEPTH);
+
+ total = g_new0 (guint64, 1);
+ *total = *(guint64*)g_private_get (&size_key);
+
+ if (g_task_set_return_on_cancel (task, FALSE))
+ g_task_return_pointer (task, total, g_free);
+}
+
+void
+file_size_async (GFile *file,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer data)
+{
+ g_autoptr(GTask) task = g_task_new (file, cancellable, callback, data);
+ g_task_set_return_on_cancel (task, TRUE);
+ g_task_run_in_thread (task, file_size_thread_func);
+}
+
+gboolean
+file_size_finish (GFile *file,
+ GAsyncResult *result,
+ guint64 *size,
+ GError **error)
+{
+ g_autofree guint64 *data = NULL;
+
+ g_return_val_if_fail (g_task_is_valid (result, file), FALSE);
+ data = g_task_propagate_pointer (G_TASK (result), error);
+ if (data == NULL)
+ return FALSE;
+ if (size != NULL)
+ *size = *data;
+ return TRUE;
+}
+
+void
+container_remove_all (GtkContainer *container)
+{
+ g_autoptr(GList) children = NULL;
+ GList *l;
+
+ children = gtk_container_get_children (container);
+ for (l = children; l; l = l->next)
+ gtk_widget_destroy (GTK_WIDGET (l->data));
+}
+
+static gchar *
+get_output_of (const gchar **argv)
+{
+ g_autofree gchar *output = NULL;
+ int status;
+
+ if (!g_spawn_sync (NULL,
+ (gchar**) argv,
+ NULL,
+ G_SPAWN_SEARCH_PATH,
+ NULL, NULL,
+ &output, NULL,
+ &status, NULL))
+ return NULL;
+
+ if (!g_spawn_check_exit_status (status, NULL))
+ return NULL;
+
+ return g_steal_pointer (&output);
+}
+
+GKeyFile *
+get_flatpak_metadata (const gchar *app_id)
+{
+ const gchar *argv[5] = { "flatpak", "info", "-m", "app", NULL };
+ g_autofree gchar *data = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GKeyFile) keyfile = NULL;
+
+ argv[3] = app_id;
+
+ data = get_output_of (argv);
+ if (data == NULL)
+ return NULL;
+
+ keyfile = g_key_file_new ();
+ if (!g_key_file_load_from_data (keyfile, data, -1, 0, &error))
+ {
+ g_warning ("%s", error->message);
+ return NULL;
+ }
+
+ return g_steal_pointer (&keyfile);
+}
+
+guint64
+get_flatpak_app_size (const gchar *app_id)
+{
+ const gchar *argv[5] = { "flatpak", "info", "-s", "app", NULL };
+ g_autofree gchar *data = NULL;
+ guint64 factor;
+ double val;
+
+ argv[3] = app_id;
+
+ data = get_output_of (argv);
+ if (data == NULL)
+ return 0;
+
+ data = g_strstrip (data);
+
+ if (g_str_has_suffix (data, "kB") || g_str_has_suffix (data, "kb"))
+ factor = 1000;
+ else if (g_str_has_suffix (data, "MB") || g_str_has_suffix (data, "Mb"))
+ factor = 1000 * 1000;
+ else if (g_str_has_suffix (data, "GB") || g_str_has_suffix (data, "Gb"))
+ factor = 1000 * 1000 * 1000;
+ else if (g_str_has_suffix (data, "KiB") || g_str_has_suffix (data, "Kib"))
+ factor = 1024;
+ else if (g_str_has_suffix (data, "MiB") || g_str_has_suffix (data, "Mib"))
+ factor = 1024 * 1024;
+ else if (g_str_has_suffix (data, "GiB") || g_str_has_suffix (data, "Gib"))
+ factor = 1024 * 1024 * 1024;
+ else
+ factor = 1;
+
+ val = g_ascii_strtod (data, NULL);
+
+ return (guint64)(val * factor);
+}
+
+guint64
+get_snap_app_size (const gchar *snap_name)
+{
+#ifdef HAVE_SNAP
+ g_autoptr(SnapdClient) client = NULL;
+ g_autoptr(SnapdSnap) snap = NULL;
+ g_autoptr(GError) error = NULL;
+
+ client = snapd_client_new ();
+ snap = snapd_client_get_snap_sync (client, snap_name, NULL, &error);
+ if (snap == NULL)
+ {
+ g_warning ("Failed to get snap size: %s", error->message);
+ return 0;
+ }
+
+ return snapd_snap_get_installed_size (snap);
+#else
+ return 0;
+#endif
+}
+
+char *
+get_app_id (GAppInfo *info)
+{
+ gchar *app_id = g_strdup (g_app_info_get_id (info));
+
+ if (g_str_has_suffix (app_id, ".desktop"))
+ app_id[strlen (app_id) - strlen (".desktop")] = '\0';
+
+ return app_id;
+}
diff --git a/panels/applications/utils.h b/panels/applications/utils.h
new file mode 100644
index 0000000..5f899d7
--- /dev/null
+++ b/panels/applications/utils.h
@@ -0,0 +1,57 @@
+/* utils.h
+ *
+ * Copyright 2018 Matthias Clasen <matthias.clasen@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
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void file_remove_async (GFile *file,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer data);
+
+gboolean file_remove_finish (GFile *file,
+ GAsyncResult *result,
+ GError **error);
+
+void file_size_async (GFile *file,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer data);
+
+gboolean file_size_finish (GFile *file,
+ GAsyncResult *result,
+ guint64 *size,
+ GError **error);
+
+void container_remove_all (GtkContainer *container);
+
+GKeyFile* get_flatpak_metadata (const gchar *app_id);
+
+guint64 get_flatpak_app_size (const gchar *app_id);
+
+guint64 get_snap_app_size (const gchar *snap_name);
+
+gchar* get_app_id (GAppInfo *info);
+
+G_END_DECLS