[gnome-control-center/wip/applications: 11/17] applications: Implement this panel



commit 4afcc5171d5e382324f2e881df2e409f2946f352
Author: Matthias Clasen <mclasen redhat com>
Date:   Fri Nov 16 18:54:15 2018 -0500

    applications: Implement this panel
    
    This is an initial implementation of most of the intended
    functionality for this panel.

 panels/applications/applications.gresource.xml |    5 +
 panels/applications/cc-action-row.c            |  219 ++++
 panels/applications/cc-action-row.h            |   39 +
 panels/applications/cc-action-row.ui           |   47 +
 panels/applications/cc-applications-panel.c    | 1614 +++++++++++++++++++++++-
 panels/applications/cc-applications-panel.css  |    7 +
 panels/applications/cc-applications-panel.ui   |  540 +++++++-
 panels/applications/cc-applications-row.c      |  103 ++
 panels/applications/cc-applications-row.h      |   34 +
 panels/applications/cc-applications-row.ui     |   28 +
 panels/applications/cc-info-row.c              |  217 ++++
 panels/applications/cc-info-row.h              |   35 +
 panels/applications/cc-info-row.ui             |   40 +
 panels/applications/cc-toggle-row.c            |  152 +++
 panels/applications/cc-toggle-row.h            |   35 +
 panels/applications/cc-toggle-row.ui           |   29 +
 panels/applications/globs.c                    |   62 +
 panels/applications/globs.h                    |   29 +
 panels/applications/meson.build                |   14 +-
 panels/applications/search.c                   |  134 ++
 panels/applications/search.h                   |   29 +
 panels/applications/utils.c                    |  163 +++
 panels/applications/utils.h                    |   36 +
 23 files changed, 3603 insertions(+), 8 deletions(-)
---
diff --git a/panels/applications/applications.gresource.xml b/panels/applications/applications.gresource.xml
index e8eaa733f..2a8fd59b4 100644
--- a/panels/applications/applications.gresource.xml
+++ b/panels/applications/applications.gresource.xml
@@ -2,5 +2,10 @@
 <gresources>
   <gresource prefix="/org/gnome/control-center/applications">
     <file preprocess="xml-stripblanks">cc-applications-panel.ui</file>
+    <file preprocess="xml-stripblanks">cc-applications-row.ui</file>
+    <file preprocess="xml-stripblanks">cc-toggle-row.ui</file>
+    <file preprocess="xml-stripblanks">cc-info-row.ui</file>
+    <file preprocess="xml-stripblanks">cc-action-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 000000000..ccb197e14
--- /dev/null
+++ b/panels/applications/cc-action-row.c
@@ -0,0 +1,219 @@
+/* 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"
+
+enum {
+  PROP_ZERO,
+  PROP_TITLE,
+  PROP_SUBTITLE,
+  PROP_ACTION,
+  PROP_ENABLED,
+  PROP_DESTRUCTIVE
+};
+
+static int activated_signal;
+
+struct _CcActionRow
+{
+  GtkListBoxRow parent;
+
+  GtkWidget *title;
+  GtkWidget *subtitle;
+  GtkWidget *button;
+};
+
+G_DEFINE_TYPE (CcActionRow, cc_action_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_action_row_finalize (GObject *object)
+{
+  //CcActionRow *row = CC_ACTION_ROW (object);
+
+  G_OBJECT_CLASS (cc_action_row_parent_class)->finalize (object);
+}
+
+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->finalize = cc_action_row_finalize;
+  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));
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/control-center/applications/cc-action-row.ui");
+
+
+  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_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);
+}
+
+static void
+clicked_cb (GtkButton *button,
+            CcActionRow *row)
+{
+  g_signal_emit (row, activated_signal, 0);
+}
+
+static void
+cc_action_row_init (CcActionRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (self), FALSE);
+  g_signal_connect (self->button, "clicked", G_CALLBACK (clicked_cb), 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 char  *name)
+{
+  gtk_label_set_label (GTK_LABEL (row->title), name);
+}
+
+void
+cc_action_row_set_subtitle (CcActionRow *row,
+                            const char  *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 char *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 000000000..7268da1e5
--- /dev/null
+++ b/panels/applications/cc-action-row.h
@@ -0,0 +1,39 @@
+/* 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 char *label);
+void         cc_action_row_set_subtitle (CcActionRow *row,
+                                         const char *label);
+void         cc_action_row_set_action (CcActionRow *row,
+                                       const char *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 000000000..0649357d4
--- /dev/null
+++ b/panels/applications/cc-action-row.ui
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="CcActionRow" parent="GtkListBoxRow">
+    <property name="visible">True</property>
+    <property name="can-focus">True</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>
+              </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>
+                <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>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/panels/applications/cc-applications-panel.c b/panels/applications/cc-applications-panel.c
index f69ca61c8..85e1ba590 100644
--- a/panels/applications/cc-applications-panel.c
+++ b/panels/applications/cc-applications-panel.c
@@ -18,26 +18,187 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
+#include <config.h>
+#include <glib/gi18n.h>
+
+#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 "list-box-helper.h"
+#include "utils.h"
+
+#include <flatpak/flatpak.h>
+
+enum {
+  PROP_0,
+  PROP_PARAMETERS
+};
 
 struct _CcApplicationsPanel
 {
   CcPanel     parent;
 
   GtkListBox *sidebar_listbox;
+  GtkWidget *header_button;
+  GtkWidget *title_label;
+  GAppInfoMonitor *monitor;
+  gulong monitor_id;
+
+  GCancellable *cancellable;
+
+  char *current_app_id;
+
+  GHashTable *globs;
+  GHashTable *search_providers;
+
+  GDBusProxy *perm_store;
+  GSettings *notification_settings;
+  GSettings *location_settings;
+  GSettings *privacy_settings;
+  GSettings *search_settings;
+
+  GtkListBox *stack;
+
+  GtkWidget *permission_section;
+  GtkWidget *permission_list;
+  GtkWidget *camera;
+  GtkWidget *no_camera;
+  GtkWidget *location;
+  GtkWidget *no_location;
+  GtkWidget *microphone;
+  GtkWidget *no_microphone;
+  GtkWidget *builtin;
+  GtkWidget *builtin_dialog;
+  GtkWidget *builtin_label;
+  GtkWidget *builtin_list;
+
+  GtkWidget *integration_section;
+  GtkWidget *integration_list;
+  GtkWidget *notification;
+  GtkWidget *sound;
+  GtkWidget *no_sound;
+  GtkWidget *search;
+  GtkWidget *no_search;
+
+  GtkWidget *handler_section;
+  GtkWidget *handler_reset;
+  GtkWidget *handler_list;
+  GtkWidget *hypertext;
+  GtkWidget *text;
+  GtkWidget *images;
+  GtkWidget *fonts;
+  GtkWidget *archives;
+  GtkWidget *packages;
+  GtkWidget *audio;
+  GtkWidget *video;
+  GtkWidget *other;
+  GtkWidget *link;
+
+  GtkWidget *usage_section;
+  GtkWidget *usage_list;
+  GtkWidget *storage;
+  GtkWidget *storage_dialog;
+  GtkWidget *storage_list;
+  GtkWidget *app;
+  GtkWidget *data;
+  GtkWidget *cache;
+  GtkWidget *total;
+  GtkWidget *clear_cache_button;
+
+  guint64 app_size;
+  guint64 cache_size;
+  guint64 data_size;
 };
 
 G_DEFINE_TYPE (CcApplicationsPanel, cc_applications_panel, CC_TYPE_PANEL)
 
+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_cancellable_cancel (self->cancellable);
+
+  G_OBJECT_CLASS (cc_applications_panel_parent_class)->dispose (object);
+}
+
 static void
 cc_applications_panel_finalize (GObject *object)
 {
-  CcApplicationsPanel *self = (CcApplicationsPanel *)object;
+  CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (object);
+
+  g_clear_object (&self->notification_settings);
+  g_clear_object (&self->location_settings);
+  g_clear_object (&self->privacy_settings);
+  g_clear_object (&self->search_settings);
+  g_clear_object (&self->cancellable);
+
+  g_free (self->current_app_id);
+  g_hash_table_unref (self->globs);
+  g_hash_table_unref (self->search_providers);
 
   G_OBJECT_CLASS (cc_applications_panel_parent_class)->finalize (object);
 }
 
+static void select_app (CcApplicationsPanel *self, const char *app_id);
+
+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, self->header_button);
+}
+
 static GtkWidget*
 cc_applications_panel_get_sidebar_widget (CcPanel *panel)
 {
@@ -45,6 +206,1337 @@ cc_applications_panel_get_sidebar_widget (CcPanel *panel)
   return GTK_WIDGET (self->sidebar_listbox);
 }
 
+static GtkWidget *
+cc_applications_panel_get_title_widget (CcPanel *panel)
+{
+  CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (panel);
+  return self->title_label;
+}
+
+/* --- misc callbacks --- */
+
+static void
+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, "privacy", NULL, &error))
+    g_warning ("Failed to switch to privacy panel: %s", error->message);
+}
+
+static void
+open_software_cb (GtkButton *button,
+                  CcApplicationsPanel *self)
+{
+  const char *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);
+}
+
+/* --- flatpak permissions and utilities --- */
+
+static char **
+get_flatpak_permissions (CcApplicationsPanel *self,
+                         const char *table,
+                         const char *id,
+                         const char *app_id)
+{
+  g_autoptr(GVariant) ret = NULL;
+  g_autoptr(GVariantIter) iter = NULL;
+  char *key;
+  GVariant *val;
+  char **permissions = 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@as}", &key, &val))
+    {
+      if (strcmp (key, app_id) == 0)
+        {
+          permissions = g_variant_dup_strv (val, NULL); 
+          break;
+        }
+    }
+
+  return permissions;
+}
+
+static void
+set_flatpak_permissions (CcApplicationsPanel *self,
+                         const char *table,
+                         const char *id,
+                         const char *app_id,
+                         const char * const *permissions)
+{
+  g_dbus_proxy_call_sync (self->perm_store,
+                          "SetPermission",
+                          g_variant_new ("(sbss^as)", table, TRUE, id, app_id, permissions),
+                          0, G_MAXINT, NULL, NULL);
+}
+
+static gboolean
+app_info_is_flatpak (GAppInfo *info)
+{
+  if (G_IS_DESKTOP_APP_INFO (info))
+    {
+      g_autofree char *marker = NULL;
+      marker = g_desktop_app_info_get_string (G_DESKTOP_APP_INFO (info), "X-Flatpak");
+      return marker != NULL;
+    }
+
+  return FALSE;
+}
+
+static GFile *
+get_flatpak_app_dir (const char *app_id,
+                     const char *subdir)
+{
+  g_autofree char *path = g_build_filename (g_get_home_dir (), ".var", "app", app_id, NULL);
+  g_autoptr(GFile) appdir = g_file_new_for_path (path);
+  return g_file_get_child (appdir, subdir);
+}
+
+/* --- search settings --- */
+
+static void
+set_search_enabled (CcApplicationsPanel *self,
+                    const char *app_id,
+                    gboolean enabled)
+{
+  gpointer key, value;
+  gboolean default_disabled;
+  g_auto(GStrv) apps = NULL;
+  g_autoptr(GPtrArray) new_apps = NULL;
+  int i;
+  g_autofree char *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 char * 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 char * const *)new_apps->pdata);
+    }
+}
+
+static gboolean
+search_enabled_for_app (CcApplicationsPanel *self,
+                        const char *app_id)
+{
+  g_autofree char *desktop_id = g_strconcat (app_id, ".desktop", NULL);
+  g_auto(GStrv) apps = g_settings_get_strv (self->search_settings, "enabled");
+  return g_strv_contains ((const char * const *)apps, desktop_id);
+}
+
+static gboolean
+search_disabled_for_app (CcApplicationsPanel *self,
+                         const char *app_id)
+{
+  g_autofree char *desktop_id = g_strconcat (app_id, ".desktop", NULL);
+  g_auto(GStrv) apps = g_settings_get_strv (self->search_settings, "disabled");
+  return g_strv_contains ((const char * const *)apps, desktop_id);
+}
+
+static void
+get_search_enabled (CcApplicationsPanel *self,
+                    const char *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 (CC_TOGGLE_ROW (self->search)));
+}
+
+/* --- notification permissions (flatpaks and non-flatpak) --- */
+
+static void
+get_notification_allowed (CcApplicationsPanel *self,
+                          const char *app_id,
+                          gboolean *set,
+                          gboolean *allowed)
+{
+  if (self->notification_settings)
+    {
+      *set = TRUE; /* FIXME */
+      *allowed = g_settings_get_boolean (self->notification_settings, "enable");
+    }
+  else
+    {
+      g_auto(GStrv) perms = get_flatpak_permissions (self, "notifications", "notification", app_id);
+      *set = perms != NULL;
+      *set = TRUE; // FIXME: needs unreleased xdg-desktop-portals to write permissions on use
+      *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 char *perms[2] = { NULL, NULL };
+
+      perms[0] = allowed ? "yes" : "no";
+      set_flatpak_permissions (self, "notifications", "notification", self->current_app_id, perms);
+    }
+}
+
+static void
+notification_cb (CcApplicationsPanel *self)
+{
+  if (self->current_app_id)
+    set_notification_allowed (self, cc_toggle_row_get_allowed (CC_TOGGLE_ROW (self->notification)));
+}
+
+static char *
+munge_app_id (const char *app_id)
+{
+  int i;
+  char *id = g_strdup (app_id);
+
+  g_strcanon (id,
+              "0123456789"
+              "abcdefghijklmnopqrstuvwxyz"
+              "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+              "-",
+              '-');
+  for (i = 0; id[i] != '\0'; i++)
+    id[i] = g_ascii_tolower (id[i]);
+
+  return id;
+}
+
+#define MASTER_SCHEMA "org.gnome.desktop.notifications"
+#define APP_SCHEMA MASTER_SCHEMA ".application"
+#define APP_PREFIX "/org/gnome/desktop/notifications/application/"
+
+static GSettings *
+get_notification_settings (const char *app_id)
+{
+  g_autofree char *munged_app_id = munge_app_id (app_id);
+  g_autofree char *path = g_strconcat (APP_PREFIX, munged_app_id, "/", NULL);
+  return g_settings_new_with_path (APP_SCHEMA, path);
+}
+
+/* --- device (microphone, camera, speaker) permissions (flatpak) --- */
+
+static void
+get_device_allowed (CcApplicationsPanel *self,
+                    const char *device,
+                    const char *app_id,
+                    gboolean *set,
+                    gboolean *allowed)
+{
+  g_auto(GStrv) perms = NULL;
+
+  perms = get_flatpak_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 char *device,
+                    gboolean allowed)
+{
+  const char *perms[2];
+
+  perms[0] = allowed ? "yes" : "no";
+  perms[1] = NULL;
+
+  set_flatpak_permissions (self, "devices", device, self->current_app_id, perms);
+}
+
+static void
+microphone_cb (CcApplicationsPanel *self)
+{
+  if (self->current_app_id)
+    set_device_allowed (self, "microphone", cc_toggle_row_get_allowed (CC_TOGGLE_ROW (self->microphone)));
+}
+
+static void
+sound_cb (CcApplicationsPanel *self)
+{
+  if (self->current_app_id)
+   set_device_allowed (self, "speakers", cc_toggle_row_get_allowed (CC_TOGGLE_ROW (self->sound)));
+}
+
+static void
+camera_cb (CcApplicationsPanel *self)
+{
+  if (self->current_app_id)
+    set_device_allowed (self, "camera", cc_toggle_row_get_allowed (CC_TOGGLE_ROW (self->camera)));
+}
+
+/* --- location permissions (flatpak) --- */
+
+static void
+get_location_allowed (CcApplicationsPanel *self,
+                      const char *app_id,
+                      gboolean *set,
+                      gboolean *allowed)
+{
+  g_auto(GStrv) perms = NULL;
+
+  perms = get_flatpak_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 char *perms[3];
+  g_autofree char *time = NULL;
+
+  // FIXME allow setting accuracy
+  perms[0] = allowed ? "EXACT" : "NONE";
+  perms[1] = "0";
+  perms[2] = NULL;
+
+  set_flatpak_permissions (self, "location", "location", self->current_app_id, perms);
+}
+
+static void
+location_cb (CcApplicationsPanel *self)
+{
+  if (self->current_app_id)
+    set_location_allowed (self, cc_toggle_row_get_allowed (CC_TOGGLE_ROW (self->location)));
+}
+
+/* --- permissions section --- */
+
+static int
+add_static_permission_row (CcApplicationsPanel *self,
+                           const char *title,
+                           const char *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 (GtkListBox    *list,
+                             GtkListBoxRow *list_row,
+                             CcApplicationsPanel *self)
+{
+  GtkWidget *row = GTK_WIDGET (list_row);
+  g_autoptr(GError) error = NULL;
+
+  if (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 char *app_id)
+{
+  g_autoptr(FlatpakInstalledRef) ref = NULL;
+  g_autoptr(GBytes) bytes = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GKeyFile) keyfile = NULL;
+  char **strv;
+  char *str;
+  int added = 0;
+  g_autofree char *text = NULL;
+  
+  ref = find_flatpak_ref (app_id);
+  bytes = flatpak_installed_ref_load_metadata (ref, NULL, NULL);
+  keyfile = g_key_file_new ();
+  if (!g_key_file_load_from_data (keyfile,
+                                  g_bytes_get_data (bytes, NULL),
+                                  g_bytes_get_size (bytes),
+                                  0, &error))
+    {
+      g_warning ("%s", error->message);
+      return FALSE;
+    }
+
+  strv = g_key_file_get_string_list (keyfile, "Context", "sockets", NULL, NULL);
+  if (strv && g_strv_contains ((const char * const*)strv, "system-bus"))
+    added += add_static_permission_row (self, _("System Bus"), _("Full access"));
+  if (strv && g_strv_contains ((const char * const*)strv, "session-bus"))
+    added += add_static_permission_row (self, _("Session Bus"), _("Full access"));
+  g_strfreev (strv);
+
+  strv = g_key_file_get_string_list (keyfile, "Context", "devices", NULL, NULL);
+  if (strv && g_strv_contains ((const char * const*)strv, "all"))
+    added += add_static_permission_row (self, _("Devices"), _("Full access to /dev"));
+  g_strfreev (strv);
+
+  strv = g_key_file_get_string_list (keyfile, "Context", "shared", NULL, NULL);
+  if (strv && g_strv_contains ((const char * const*)strv, "network"))
+    added += add_static_permission_row (self, _("Network"), _("Has network access"));
+  g_strfreev (strv);
+
+  strv = g_key_file_get_string_list (keyfile, "Context", "filesystems", NULL, NULL);
+  if (strv && (g_strv_contains ((const char * const *)strv, "home") ||
+               g_strv_contains ((const char * const *)strv, "home:rw")))
+    added += add_static_permission_row (self, _("Home"), _("Full access"));
+  else if (strv && g_strv_contains ((const char * const *)strv, "home:ro"))
+    added += add_static_permission_row (self, _("Home"), _("Read-only"));
+  if (strv && (g_strv_contains ((const char * const *)strv, "host") ||
+               g_strv_contains ((const char * const *)strv, "host:rw")))
+    added += add_static_permission_row (self, _("File System"), _("Full access"));
+  else if (strv && g_strv_contains ((const char * const *)strv, "host:ro"))
+    added += add_static_permission_row (self, _("File System"), _("Read-only"));
+  g_strfreev (strv);
+
+  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"));
+  g_free (str);
+
+  gtk_widget_set_visible (self->builtin, added > 0);
+
+  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 (GTK_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 char *app_id = get_app_id (info);
+  gboolean disabled, allowed, set;
+  gboolean has_any = FALSE;
+
+  if (!app_info_is_flatpak (info))
+    {
+      gtk_widget_hide (self->permission_section);
+      return;
+    }
+
+  disabled = g_settings_get_boolean (self->privacy_settings, "disable-camera");
+  get_device_allowed (self, "camera", app_id, &set, &allowed);
+  cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->camera), allowed);
+  gtk_widget_set_visible (self->camera, set && !disabled);
+  gtk_widget_set_visible (self->no_camera, set && disabled);
+  has_any |= set;
+
+  disabled = g_settings_get_boolean (self->privacy_settings, "disable-microphone");
+  get_device_allowed (self, "microphone", app_id, &set, &allowed);
+  cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->microphone), allowed);
+  gtk_widget_set_visible (self->microphone, set && !disabled);
+  gtk_widget_set_visible (self->no_microphone, set && disabled);
+  has_any |= set;
+
+  disabled = !g_settings_get_boolean (self->location_settings, "enabled");
+  get_location_allowed (self, app_id, &set, &allowed);
+  cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->location), allowed);
+  gtk_widget_set_visible (self->location, set && !disabled);
+  gtk_widget_set_visible (self->no_location, set && disabled);
+  has_any |= set;
+
+  remove_static_permissions (self);
+  has_any |= add_static_permissions (self, info, app_id);
+
+  gtk_widget_set_visible (self->permission_section, has_any);
+}
+
+/* --- integration section --- */
+
+static void
+update_integration_section (CcApplicationsPanel *self,
+                            GAppInfo *info)
+{
+  g_autofree char *app_id = get_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 (CC_TOGGLE_ROW (self->search), allowed);
+  gtk_widget_set_visible (self->search, set && !disabled);
+  gtk_widget_set_visible (self->no_search, set && disabled);
+
+  if (app_info_is_flatpak (info))
+    {
+      g_clear_object (&self->notification_settings);
+      get_notification_allowed (self, app_id, &set, &allowed);
+      cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->notification), allowed);
+      gtk_widget_set_visible (self->notification, set);
+      has_any |= set;
+
+      disabled = g_settings_get_boolean (self->privacy_settings, "disable-sound-output");
+      get_device_allowed (self, "speakers", app_id, &set, &allowed);
+      cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->sound), allowed);
+      gtk_widget_set_visible (self->sound, set && !disabled);
+      gtk_widget_set_visible (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 (CC_TOGGLE_ROW (self->notification), allowed);
+      gtk_widget_set_visible (self->notification, set);
+      has_any |= set;
+
+      gtk_widget_hide (self->sound);
+      gtk_widget_hide (self->no_sound);
+    }
+
+  gtk_widget_set_visible (self->integration_section, has_any);
+}
+
+/* --- handler section --- */
+
+static void
+unset_cb (CcActionRow *row,
+          CcApplicationsPanel *self)
+{
+  const char *type;
+  GtkListBoxRow *selected;
+  GAppInfo *info;
+
+  selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (self->sidebar_listbox));
+  info = cc_applications_row_get_info (CC_APPLICATIONS_ROW (selected));  
+
+  type = (const char *)g_object_get_data (G_OBJECT (row), "type");
+
+  g_app_info_remove_supports_type (info, type, NULL);
+}
+
+static void
+update_group_row_count (GtkWidget *row,
+                        int delta)
+{
+  int count;
+  g_autofree char *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,
+            GtkWidget *after,
+            const char *type)
+{
+  CcActionRow *row = NULL;
+  int 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
+    {
+      char *scheme = strrchr (type, '/') + 1;
+      g_autofree char *title = g_strdup_printf (_("%s Links"), scheme);
+      g_autofree char *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 (row, "activated", G_CALLBACK (unset_cb), self);
+
+  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 (GTK_LIST_BOX (self->handler_list), GTK_WIDGET (row), pos);
+  update_group_row_count (after, 1);
+}
+
+static void
+add_file_type (CcApplicationsPanel *self,
+               GtkWidget *after,
+               const char *type)
+{
+  CcActionRow *row;
+  const char *desc;
+  int pos;
+  const char *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 (row, "activated", G_CALLBACK (unset_cb), self);
+
+  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 (GTK_LIST_BOX (self->handler_list), GTK_WIDGET (row), pos);
+  update_group_row_count (after, 1);
+}
+
+static gboolean
+is_hypertext_type (const char *type)
+{
+  const char *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,
+                  GtkWidget **row,
+                  const char *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 (GTK_LIST_BOX (self->handler_list), GTK_WIDGET (r), -1);
+      *row = GTK_WIDGET (r);
+    }
+}
+
+static void
+add_link_type (CcApplicationsPanel *self,
+               const char *type)
+{
+  ensure_group_row (self, &self->link, _("Links"));
+  add_scheme (self, self->link, type);
+}
+
+static void
+add_hypertext_type (CcApplicationsPanel *self,
+                    const char *type)
+{
+  ensure_group_row (self, &self->hypertext, _("Hypertext Files"));
+  add_file_type (self, self->hypertext, type);
+}
+
+static gboolean
+is_text_type (const char *type)
+{
+  return g_content_type_is_a (type, "text/*");
+}
+
+static void
+add_text_type (CcApplicationsPanel *self,
+               const char *type)
+{
+  ensure_group_row (self, &self->text, _("Text Files"));
+  add_file_type (self, self->text, type);
+}
+
+static gboolean
+is_image_type (const char *type)
+{
+  return g_content_type_is_a (type, "image/*");
+}
+
+static void
+add_image_type (CcApplicationsPanel *self,
+                const char *type)
+{
+  ensure_group_row (self, &self->images, _("Image Files"));
+  add_file_type (self, self->images, type);
+}
+
+static gboolean
+is_font_type (const char *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 char *type)
+{
+  ensure_group_row (self, &self->fonts, _("Font Files"));
+  add_file_type (self, self->fonts, type);
+}
+
+static gboolean
+is_archive_type (const char *type)
+{
+  const char *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 char *type)
+{
+  ensure_group_row (self, &self->archives, _("Archive Files"));
+  add_file_type (self, self->archives, type);
+}
+
+static gboolean
+is_package_type (const char *type)
+{
+  const char *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 char *type)
+{
+  ensure_group_row (self, &self->packages, _("Package Files"));
+  add_file_type (self, self->packages, type);
+}
+
+static gboolean
+is_audio_type (const char *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 char *type)
+{
+  ensure_group_row (self, &self->audio, _("Audio Files"));
+  add_file_type (self, self->audio, type);
+}
+
+static gboolean
+is_video_type (const char *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 char *type)
+{
+  ensure_group_row (self, &self->video, _("Video Files"));
+  add_file_type (self, self->video, type);
+}
+
+static void
+add_other_type (CcApplicationsPanel *self,
+                const char *type)
+{
+  ensure_group_row (self, &self->other, _("Other Files"));
+  add_file_type (self, self->other, type);
+}
+
+static void
+add_handler_row (CcApplicationsPanel *self,
+                 const char *type)
+{
+  gtk_widget_show (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 (GtkListBox    *list,
+                          GtkListBoxRow *list_row,
+                          CcApplicationsPanel *self)
+{
+  GtkWidget *row = GTK_WIDGET (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 (CC_INFO_ROW (row),
+                              !cc_info_row_get_expanded (CC_INFO_ROW (row)));
+}
+
+static gboolean
+app_info_recommended_for (GAppInfo *info,
+                          const char *type)
+{
+  /* this is horribly inefficient. I blame the mime system */
+  GList *list, *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;
+        }
+    }
+
+  g_list_free_full (list, g_object_unref);
+
+  return ret;
+}
+
+static void
+handler_reset_cb (GtkButton *button, CcApplicationsPanel *self)
+{
+  GtkListBoxRow *selected;
+  GAppInfo *info;
+  const char **types;
+  int i;
+
+  selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (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++)
+    {
+      char *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)
+{
+  const char **types;
+  int i;
+  g_autoptr(GHashTable) hash = NULL;
+
+  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 (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 (self->handler_reset, FALSE);
+  for (i = 0; types[i]; i++)
+    {
+      char *ctype = g_content_type_from_mime_type (types[i]);
+      if (g_hash_table_contains (hash, ctype))
+        {
+          g_free (ctype);
+          continue;
+        }
+      if (!app_info_recommended_for (info, ctype))
+        {
+          gtk_widget_set_sensitive (self->handler_reset, TRUE);
+          g_free (ctype);
+          continue;
+        }
+      g_hash_table_add (hash, ctype);
+      add_handler_row (self, ctype);
+    }
+}
+
+/* --- usage section --- */
+
+static void
+storage_row_activated_cb (GtkListBox    *list,
+                          GtkListBoxRow *list_row,
+                          CcApplicationsPanel *self)
+{
+  GtkWidget *row = GTK_WIDGET (list_row);
+  g_autoptr(GError) error = NULL;
+
+  if (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)
+{
+  guint64 total;
+  g_autofree char *formatted_size = NULL;
+
+  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;
+  guint64 *size;
+  g_autofree char *formatted_size = NULL;
+
+  size = g_object_get_data (G_OBJECT (res), "size");
+  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 (self->clear_cache_button, self->cache_size > 0);
+
+  update_total_size (self);
+}
+
+static void
+update_cache_row (CcApplicationsPanel *self,
+                  const char          *app_id)
+{
+  g_autoptr(GFile) dir = get_flatpak_app_dir (app_id, "cache");
+  g_object_set (self->cache, "info", "...", NULL);
+  file_size_async (dir, set_cache_size, self);
+}
+
+static void
+set_data_size (GObject *source,
+               GAsyncResult *res,
+               gpointer data)
+{
+  CcApplicationsPanel *self = data;
+  guint64 *size;
+  g_autofree char *formatted_size = NULL;
+
+  size = g_object_get_data (G_OBJECT (res), "size");
+  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 char          *app_id)
+{
+  g_autoptr(GFile) dir = get_flatpak_app_dir (app_id, "data");
+  g_object_set (self->data, "info", "...", NULL);
+  file_size_async (dir, set_data_size, self);
+}
+
+static void
+cache_cleared (GObject *source,
+               GAsyncResult *res,
+               gpointer data)
+{
+  CcApplicationsPanel *self = data;
+  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, cache_cleared, self);
+}
+static void
+update_app_row (CcApplicationsPanel *self,
+                const char *app_id)
+{
+  g_autofree char *formatted_size = NULL;
+
+  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_flatpak_sizes (CcApplicationsPanel *self,
+                      const char *app_id)
+{
+  gtk_widget_set_sensitive (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)
+{
+  if (app_info_is_flatpak (info))
+    {
+      g_autofree char *app_id = get_app_id (info);
+      gtk_widget_show (self->usage_section);
+      update_flatpak_sizes (self, app_id);
+    }
+  else
+    {
+      gtk_widget_hide (self->usage_section);
+    }
+}
+
+/* --- panel setup --- */
+
+static void
+update_panel (CcApplicationsPanel *self,
+              GtkListBoxRow *row)
+{
+  GAppInfo *info;
+
+  if (self->perm_store == NULL)
+    {
+      g_print ("no perm store proxy yet, come back later\n");
+      return;
+    }
+
+  if (row == NULL)
+    {
+      gtk_label_set_label (GTK_LABEL (self->title_label), _("Applications"));
+      gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "empty");
+      gtk_widget_hide (self->header_button);
+      return;
+    }
+
+  info = cc_applications_row_get_info (CC_APPLICATIONS_ROW (row));
+
+  gtk_label_set_label (GTK_LABEL (self->title_label), g_app_info_get_display_name (info));
+  gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "settings");
+  gtk_widget_show (self->header_button);
+
+  g_clear_pointer (&self->current_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);
+}
+
+static void
+populate_applications (CcApplicationsPanel *self)
+{
+  GList *infos, *l;
+
+  container_remove_all (GTK_CONTAINER (self->sidebar_listbox));
+
+  infos = g_app_info_get_all ();
+
+  for (l = infos; l; l = l->next)
+    {
+      GAppInfo *info = l->data;
+      GtkWidget *row;
+      g_autofree char *id = NULL;
+
+      if (!g_app_info_should_show (info))
+        continue;
+
+      row = GTK_WIDGET (cc_applications_row_new (info));
+      gtk_list_box_insert (GTK_LIST_BOX (self->sidebar_listbox), row, -1);
+
+      id = get_app_id (info);
+      if (g_strcmp0 (id, self->current_app_id) == 0)
+        gtk_list_box_select_row (GTK_LIST_BOX (self->sidebar_listbox), GTK_LIST_BOX_ROW (row));
+    }
+
+  g_list_free_full (infos, g_object_unref);
+}
+
+static int
+compare_rows (GtkListBoxRow *row1,
+              GtkListBoxRow *row2,
+              gpointer       data)
+{
+  const char *key1 = cc_applications_row_get_sort_key (CC_APPLICATIONS_ROW (row1));
+  const char *key2 = cc_applications_row_get_sort_key (CC_APPLICATIONS_ROW (row2));
+
+  return strcmp (key1, key2);
+}
+
+static void
+apps_changed (GAppInfoMonitor *monitor,
+              CcApplicationsPanel *self)
+{
+  populate_applications (self);
+}
+
+static void
+row_selected_cb (GtkListBox *list,
+                 GtkListBoxRow *row,
+                 CcApplicationsPanel *self)
+{
+  update_panel (self, row);
+}
+
+static void
+on_perm_store_ready (GObject *source_object,
+                     GAsyncResult *res,
+                     gpointer data)
+{
+  CcApplicationsPanel *self = data;
+  GDBusProxy *proxy;
+  g_autoptr(GError) error = NULL;
+
+  proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+  if (proxy == NULL)
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+          g_warning ("Failed to connect to flatpak 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 char *app_id)
+{
+  GList *children, *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;
+        }
+    }
+  g_list_free (children);
+}
+
+
 static void
 cc_applications_panel_class_init (CcApplicationsPanelClass *klass)
 {
@@ -52,19 +1544,139 @@ cc_applications_panel_class_init (CcApplicationsPanelClass *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, sidebar_listbox);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, title_label);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, header_button);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, stack);
+  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, camera);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_camera);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, location);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_location);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, microphone);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_microphone);
+  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, integration_section);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, integration_list);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, notification);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sound);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_sound);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, search);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_search);
+  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, usage_section);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, usage_list);
+  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, app);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, data);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, cache);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, total);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, clear_cache_button);
+
+  gtk_widget_class_bind_template_callback (widget_class, camera_cb);
+  gtk_widget_class_bind_template_callback (widget_class, location_cb);
+  gtk_widget_class_bind_template_callback (widget_class, microphone_cb);
+  gtk_widget_class_bind_template_callback (widget_class, search_cb);
+  gtk_widget_class_bind_template_callback (widget_class, notification_cb);
+  gtk_widget_class_bind_template_callback (widget_class, 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);
 }
 
 static void
 cc_applications_panel_init (CcApplicationsPanel *self)
 {
+  g_autoptr(GtkStyleProvider) provider = NULL;
+
   g_resources_register (cc_applications_get_resource ());
 
   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 (self->sidebar_listbox, "row-selected",
+                    G_CALLBACK (row_selected_cb), self);
+
+  g_signal_connect (self->header_button, "clicked", G_CALLBACK (open_software_cb), self);
+
+  gtk_list_box_set_header_func (GTK_LIST_BOX (self->permission_list),
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+
+  gtk_list_box_set_header_func (GTK_LIST_BOX (self->integration_list),
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+
+  gtk_list_box_set_header_func (GTK_LIST_BOX (self->handler_list),
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+
+  gtk_list_box_set_header_func (GTK_LIST_BOX (self->usage_list),
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+
+  gtk_list_box_set_header_func (GTK_LIST_BOX (self->builtin_list),
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+
+  gtk_list_box_set_header_func (GTK_LIST_BOX (self->storage_list),
+                                cc_list_box_update_header_func,
+                                NULL, NULL);
+
+  gtk_list_box_set_sort_func (GTK_LIST_BOX (self->sidebar_listbox),
+                              compare_rows,
+                              NULL, NULL);
+
+  self->location_settings = g_settings_new ("org.gnome.system.location");
+  self->privacy_settings = g_settings_new ("org.gnome.desktop.privacy");
+  self->search_settings = g_settings_new ("org.gnome.desktop.search-providers");
+
+  populate_applications (self);
+
+  self->monitor = g_app_info_monitor_get ();
+  self->monitor_id = g_signal_connect (self->monitor, "changed", G_CALLBACK (apps_changed), self);
+
+  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",
+                            self->cancellable,
+                            on_perm_store_ready,
+                            self);
+
+  self->globs = parse_globs ();
+  self->search_providers = parse_search_providers ();
 }
diff --git a/panels/applications/cc-applications-panel.css b/panels/applications/cc-applications-panel.css
new file mode 100644
index 000000000..c28771d9f
--- /dev/null
+++ b/panels/applications/cc-applications-panel.css
@@ -0,0 +1,7 @@
+.section-title {
+  font-weight: bold;
+}
+
+.section-subtitle {
+  opacity: 0.55;
+}
diff --git a/panels/applications/cc-applications-panel.ui b/panels/applications/cc-applications-panel.ui
index 188f61f3e..1707bee78 100644
--- a/panels/applications/cc-applications-panel.ui
+++ b/panels/applications/cc-applications-panel.ui
@@ -2,12 +2,544 @@
 <interface>
   <template class="CcApplicationsPanel" parent="CcPanel">
     <property name="visible">True</property>
-    <property name="can_focus">False</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="GtkStack" id="stack">
+            <property name="visible">1</property>
+            <child>
+              <object class="GtkBox">
+                <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>
+                  <packing>
+                    <property name="fill">0</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="name">empty</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">1</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">1</property>
+                    <property name="hexpand">1</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">1</property>
+                    <property name="orientation">vertical</property>
+                    <property name="margin-top">32</property>
+                    <property name="margin-bottom">32</property>
+                    <property name="margin-left">24</property>
+                    <property name="margin-right">24</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"/>
+                            <child>
+                              <object class="CcToggleRow" id="camera">
+                                <property name="title" translatable="yes">Camera</property>
+                                <signal name="notify::allowed" handler="camera_cb" 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" 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" 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" 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" 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" swapped="yes"/>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="CcToggleRow" id="sound">
+                                <property name="title" translatable="yes">Sounds</property>
+                                <signal name="notify::allowed" handler="sound_cb" 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>
+                            <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>
+                            </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"/>
+                            <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"/>
+                            <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>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">1</property>
+                    <property name="hexpand">1</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="name">settings</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
   </template>
-
-  <!-- Sidebar listbox -->
+  <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>
   <object class="GtkListBox" id="sidebar_listbox">
-    <property name="visible">True</property>
+    <property name="visible">1</property>
     <property name="selection-mode">browse</property>
   </object>
+  <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>
+  <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="GtkLabel">
+            <property name="visible">1</property>
+            <property name="wrap">1</property>
+            <property name="max-width-chars">50</property>
+            <property name="label" translatable="yes">Something witty about caching.</property>
+          </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" 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 000000000..a62f5dafc
--- /dev/null
+++ b/panels/applications/cc-applications-row.c
@@ -0,0 +1,103 @@
+/* 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;
+  char *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 *row = CC_APPLICATIONS_ROW (object);
+
+  g_object_unref (row->info);
+  g_free (row->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 *row;
+  char *key;
+
+  row = g_object_new (CC_TYPE_APPLICATIONS_ROW, NULL);
+
+  row->info = g_object_ref (info);
+
+  key = g_utf8_casefold (g_app_info_get_display_name (info), -1);
+  row->sortkey = g_utf8_collate_key (key, -1);
+  g_free (key);
+
+  gtk_image_set_from_gicon (GTK_IMAGE (row->image), g_app_info_get_icon (info), GTK_ICON_SIZE_BUTTON);
+  gtk_label_set_label (GTK_LABEL (row->label), g_app_info_get_display_name (info));
+
+  return row;
+}
+
+GAppInfo *
+cc_applications_row_get_info (CcApplicationsRow *row)
+{
+  return row->info;
+}
+
+const char *
+cc_applications_row_get_sort_key (CcApplicationsRow *row)
+{
+  return row->sortkey;
+}
diff --git a/panels/applications/cc-applications-row.h b/panels/applications/cc-applications-row.h
new file mode 100644
index 000000000..d3d85ec37
--- /dev/null
+++ b/panels/applications/cc-applications-row.h
@@ -0,0 +1,34 @@
+/* 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 char        *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 000000000..be66714c7
--- /dev/null
+++ b/panels/applications/cc-applications-row.ui
@@ -0,0 +1,28 @@
+<?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">12</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkImage" id="image">
+            <property name="visible">1</property>
+            <style>
+              <class name="sidebar-icon"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label">
+            <property name="visible">1</property>
+            <property name="xalign">0</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 000000000..75dd601a9
--- /dev/null
+++ b/panels/applications/cc-info-row.c
@@ -0,0 +1,217 @@
+/* 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"
+
+enum {
+  PROP_ZERO,
+  PROP_TITLE,
+  PROP_USE_MARKUP,
+  PROP_INFO,
+  PROP_HAS_EXPANDER,
+  PROP_IS_LINK,
+  PROP_EXPANDED
+};
+
+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)
+
+static void
+cc_info_row_finalize (GObject *object)
+{
+  //CcInfoRow *row = CC_INFO_ROW (object);
+
+  G_OBJECT_CLASS (cc_info_row_parent_class)->finalize (object);
+}
+
+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->finalize = cc_info_row_finalize;
+  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 000000000..c0961ed7f
--- /dev/null
+++ b/panels/applications/cc-info-row.h
@@ -0,0 +1,35 @@
+/* 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 000000000..11ce000d2
--- /dev/null
+++ b/panels/applications/cc-info-row.ui
@@ -0,0 +1,40 @@
+<?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>
+          </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-toggle-row.c b/panels/applications/cc-toggle-row.c
new file mode 100644
index 000000000..2653d9429
--- /dev/null
+++ b/panels/applications/cc-toggle-row.c
@@ -0,0 +1,152 @@
+/* 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"
+
+enum {
+  PROP_ZERO,
+  PROP_TITLE,
+  PROP_ALLOWED
+};
+
+struct _CcToggleRow
+{
+  GtkListBoxRow parent;
+
+  GtkWidget *title;
+  GtkWidget *toggle;
+};
+
+G_DEFINE_TYPE (CcToggleRow, cc_toggle_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_toggle_row_finalize (GObject *object)
+{
+  //CcToggleRow *row = CC_TOGGLE_ROW (object);
+
+  G_OBJECT_CLASS (cc_toggle_row_parent_class)->finalize (object);
+}
+
+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
+changed_cb (CcToggleRow *row)
+{
+  g_object_notify (G_OBJECT (row), "allowed");
+}
+
+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->finalize = cc_toggle_row_finalize;
+  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 000000000..57cd76fcb
--- /dev/null
+++ b/panels/applications/cc-toggle-row.h
@@ -0,0 +1,35 @@
+/* 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 000000000..679bf6827
--- /dev/null
+++ b/panels/applications/cc-toggle-row.ui
@@ -0,0 +1,29 @@
+<?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>
+          </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 000000000..97244893c
--- /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 char * const *dirs;
+  int 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 char *file = g_build_filename (dirs[i], "mime", "globs", NULL);
+      g_autofree char *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 000000000..afdf9ddcc
--- /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/meson.build b/panels/applications/meson.build
index 49e9e6088..316a84892 100644
--- a/panels/applications/meson.build
+++ b/panels/applications/meson.build
@@ -17,7 +17,15 @@ i18n.merge_file(
   install_dir : control_center_desktopdir
 )
 
-sources = files('cc-applications-panel.c')
+sources = files('cc-applications-panel.c',
+                'cc-applications-row.c',
+                'cc-toggle-row.c',
+                'cc-info-row.c',
+                'cc-action-row.c',
+                'utils.c',
+                'globs.c',
+                'search.c'
+)
 
 resource_data = files('cc-applications-panel.ui')
 
@@ -29,14 +37,14 @@ sources += gnome.compile_resources(
         export : true
 )
 
-deps = common_deps
+deps = common_deps + [ dependency('flatpak', version: '>= 1.0.0') ]
 
 cflags += '-DGNOMELOCALEDIR="@0@"'.format(control_center_localedir)
 
 panels_libs += static_library(
            cappletname,
               sources : sources,
-  include_directories : top_inc,
+  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 000000000..fe05b7fc0
--- /dev/null
+++ b/panels/applications/search.c
@@ -0,0 +1,134 @@
+/* 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_autofree gchar *path = NULL;
+  g_autofree gchar *app_id = NULL;
+  g_autoptr(GKeyFile) keyfile = NULL;
+  g_autoptr(GAppInfo) app_info = NULL;
+  g_autoptr(GError) error = 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 char *system_dir)
+{
+  g_autofree char *providers_path = NULL;
+  g_autoptr(GFile) providers_location = NULL;
+  g_autoptr(GFileEnumerator) enumerator = NULL;
+  g_autoptr(GError) error = 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 char * const *dirs;
+  int 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 000000000..99a1a1d63
--- /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 000000000..21cbbbbb8
--- /dev/null
+++ b/panels/applications/utils.c
@@ -0,0 +1,163 @@
+/* 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>
+#include <flatpak/flatpak.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ftw.h>
+
+#include "utils.h"
+
+static int
+ftw_remove_cb (const char *path, const struct stat *sb, int 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 char *path = g_file_get_path (file);
+
+  nftw (path, ftw_remove_cb, 20, FTW_DEPTH);
+}
+
+void
+file_remove_async (GFile *file,
+                   GAsyncReadyCallback callback,
+                   gpointer data)
+{
+  g_autoptr(GTask) task = g_task_new (file, NULL, callback, data);
+  g_task_run_in_thread (task, file_remove_thread_func);
+}
+
+static GPrivate size_key = G_PRIVATE_INIT (g_free);
+
+static int
+ftw_size_cb (const char *path, const struct stat *sb, int 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 char *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);
+
+  g_object_set_data_full (G_OBJECT (task), "size", total, g_free);
+}
+
+void
+file_size_async (GFile *file,
+                 GAsyncReadyCallback callback,
+                 gpointer data)
+{
+  g_autoptr(GTask) task = g_task_new (file, NULL, callback, data);
+  g_task_run_in_thread (task, file_size_thread_func);
+}
+
+void
+container_remove_all (GtkContainer *container)
+{
+  GList *children, *l;
+
+  children = gtk_container_get_children (container);
+  for (l = children; l; l = l->next)
+    {
+      gtk_widget_destroy (GTK_WIDGET (l->data));
+    }
+
+  g_list_free (children);
+}
+
+FlatpakInstalledRef *
+find_flatpak_ref (const char *app_id)
+{
+  g_autoptr(FlatpakInstallation) inst = NULL;
+  g_autoptr(GPtrArray) array = NULL;
+  FlatpakInstalledRef *ref;
+  int i;
+
+  inst = flatpak_installation_new_user (NULL, NULL);
+  ref = flatpak_installation_get_current_installed_app (inst, app_id, NULL, NULL);
+  if (ref)
+    return ref;
+
+  array = flatpak_get_system_installations (NULL, NULL);
+  for (i = 0; i < array->len; i++)
+    {
+      FlatpakInstallation *si = g_ptr_array_index (array, i);
+      ref = flatpak_installation_get_current_installed_app (si, app_id, NULL, NULL);
+      if (ref)
+        return ref;
+    }
+
+  return NULL;
+}
+
+guint64
+get_flatpak_app_size (const char *app_id)
+{
+  g_autoptr(FlatpakInstalledRef) ref = NULL;
+
+  ref = find_flatpak_ref (app_id);
+  if (ref)
+    return flatpak_installed_ref_get_installed_size (ref);
+
+  return 0;
+}
+
+char *
+get_app_id (GAppInfo *info)
+{
+  char *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 000000000..60f5c941f
--- /dev/null
+++ b/panels/applications/utils.h
@@ -0,0 +1,36 @@
+/* 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>
+#include <flatpak/flatpak.h>
+
+G_BEGIN_DECLS
+
+void file_remove_async (GFile *file, GAsyncReadyCallback callback, gpointer data);
+void file_size_async (GFile *file, GAsyncReadyCallback callback, gpointer data);
+void container_remove_all (GtkContainer *container);
+FlatpakInstalledRef *find_flatpak_ref (const char *app_id);
+guint64 get_flatpak_app_size (const char *app_id);
+char *get_app_id (GAppInfo *info);
+
+G_END_DECLS


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]