[gnome-control-center/wip/applications: 5/7] Introduce Applications panel



commit 248499584dc5fe7687a57f5d8bd1c24eb410335e
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Fri Nov 16 15:42:54 2018 -0200

    Introduce Applications panel
    
    This is an initial implementation of most of the
    intended functionality for this panel. Flatpak
    integration itself is implemented by spawning
    "flatpak info -m $appid", which gives us the
    metadata in key file format, and allows flatpak
    to be a runtime dependency.
    
    Even after removing the .desktop suffix, there can
    still be a difference between the app ID generated
    in this way and the flatpak ID, since flatpaks are
    allowed to export files whose prefix is the flatpak
    ID. Fix this by pulling the X-Flatpak key out of
    the desktop file. This would cause trouble for
    org.libreoffice.LibreOffice-math.

 panels/applications/applications.gresource.xml     |   11 +
 panels/applications/cc-action-row.c                |  218 +++
 panels/applications/cc-action-row.h                |   42 +
 panels/applications/cc-action-row.ui               |   49 +
 panels/applications/cc-applications-panel.c        | 1703 ++++++++++++++++++++
 panels/applications/cc-applications-panel.css      |    7 +
 panels/applications/cc-applications-panel.h        |   30 +
 panels/applications/cc-applications-panel.ui       |  531 ++++++
 panels/applications/cc-applications-row.c          |  102 ++
 panels/applications/cc-applications-row.h          |   36 +
 panels/applications/cc-applications-row.ui         |   30 +
 panels/applications/cc-info-row.c                  |  215 +++
 panels/applications/cc-info-row.h                  |   37 +
 panels/applications/cc-info-row.ui                 |   40 +
 panels/applications/cc-toggle-row.c                |  144 ++
 panels/applications/cc-toggle-row.h                |   37 +
 panels/applications/cc-toggle-row.ui               |   29 +
 panels/applications/globs.c                        |   62 +
 panels/applications/globs.h                        |   29 +
 .../gnome-applications-panel.desktop.in.in         |   15 +
 panels/applications/meson.build                    |   49 +
 panels/applications/search.c                       |  133 ++
 panels/applications/search.h                       |   29 +
 panels/applications/utils.c                        |  209 +++
 panels/applications/utils.h                        |   44 +
 panels/meson.build                                 |    1 +
 shell/cc-panel-list.c                              |    1 +
 shell/cc-panel-loader.c                            |    2 +
 28 files changed, 3835 insertions(+)
---
diff --git a/panels/applications/applications.gresource.xml b/panels/applications/applications.gresource.xml
new file mode 100644
index 000000000..d23f1b2e0
--- /dev/null
+++ b/panels/applications/applications.gresource.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/control-center/applications">
+    <file preprocess="xml-stripblanks">cc-action-row.ui</file>
+    <file preprocess="xml-stripblanks">cc-applications-panel.ui</file>
+    <file preprocess="xml-stripblanks">cc-applications-row.ui</file>
+    <file preprocess="xml-stripblanks">cc-info-row.ui</file>
+    <file preprocess="xml-stripblanks">cc-toggle-row.ui</file>
+    <file>cc-applications-panel.css</file>
+  </gresource>
+</gresources>
diff --git a/panels/applications/cc-action-row.c b/panels/applications/cc-action-row.c
new file mode 100644
index 000000000..b541f79a7
--- /dev/null
+++ b/panels/applications/cc-action-row.c
@@ -0,0 +1,218 @@
+/* cc-action-row.c
+ *
+ * Copyright 2018 Matthias Clasen <matthias clasen gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include "cc-action-row.h"
+#include "cc-applications-resources.h"
+
+struct _CcActionRow
+{
+  GtkListBoxRow  parent;
+
+  GtkWidget     *title;
+  GtkWidget     *subtitle;
+  GtkWidget     *button;
+};
+
+G_DEFINE_TYPE (CcActionRow, cc_action_row, GTK_TYPE_LIST_BOX_ROW)
+
+static int activated_signal;
+
+enum
+{
+  PROP_0,
+  PROP_TITLE,
+  PROP_SUBTITLE,
+  PROP_ACTION,
+  PROP_ENABLED,
+  PROP_DESTRUCTIVE
+};
+
+static void
+clicked_cb (CcActionRow *row)
+{
+  g_signal_emit (row, activated_signal, 0);
+}
+
+static void
+cc_action_row_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  CcActionRow *row = CC_ACTION_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_TITLE:
+      g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->title)));
+      break;
+
+    case PROP_SUBTITLE:
+      g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->subtitle)));
+      break;
+
+    case PROP_ACTION:
+      g_value_set_string (value, gtk_button_get_label (GTK_BUTTON (row->button)));
+      break;
+
+    case PROP_ENABLED:
+      g_value_set_boolean (value, gtk_widget_get_sensitive (row->button));
+      break;
+
+    case PROP_DESTRUCTIVE:
+      g_value_set_boolean (value,
+                           gtk_style_context_has_class (gtk_widget_get_style_context (row->button), 
"destructive-action"));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+cc_action_row_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  CcActionRow *row = CC_ACTION_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_TITLE:
+      gtk_label_set_label (GTK_LABEL (row->title), g_value_get_string (value));
+      break;
+
+    case PROP_SUBTITLE:
+      gtk_label_set_label (GTK_LABEL (row->subtitle), g_value_get_string (value));
+      gtk_widget_set_visible (row->subtitle, strlen (g_value_get_string (value)) > 0);
+      break;
+
+    case PROP_ACTION:
+      gtk_button_set_label (GTK_BUTTON (row->button), g_value_get_string (value));
+      break;
+
+    case PROP_ENABLED:
+      gtk_widget_set_sensitive (row->button, g_value_get_boolean (value));
+      break;
+
+    case PROP_DESTRUCTIVE:
+      if (g_value_get_boolean (value))
+        gtk_style_context_add_class (gtk_widget_get_style_context (row->button), "destructive-action");
+      else
+        gtk_style_context_remove_class (gtk_widget_get_style_context (row->button), "destructive-action");
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+cc_action_row_class_init (CcActionRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->get_property = cc_action_row_get_property;
+  object_class->set_property = cc_action_row_set_property;
+
+  g_object_class_install_property (object_class,
+                                   PROP_TITLE,
+                                   g_param_spec_string ("title", "title", "title",
+                                                        NULL, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_SUBTITLE,
+                                   g_param_spec_string ("subtitle", "subtitle", "subtitle",
+                                                        NULL, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_ACTION,
+                                   g_param_spec_string ("action", "action", "action",
+                                                        NULL, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_ENABLED,
+                                   g_param_spec_boolean ("enabled", "enabled", "enabled",
+                                                         TRUE, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_DESTRUCTIVE,
+                                   g_param_spec_boolean ("destructive", "destructive", "destructive",
+                                                         FALSE, G_PARAM_READWRITE));
+
+  activated_signal = g_signal_new ("activated",
+                                   G_OBJECT_CLASS_TYPE (object_class),
+                                   G_SIGNAL_RUN_FIRST,
+                                   0,
+                                   NULL, NULL,
+                                   NULL,
+                                   G_TYPE_NONE, 0);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/control-center/applications/cc-action-row.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, CcActionRow, title);
+  gtk_widget_class_bind_template_child (widget_class, CcActionRow, subtitle);
+  gtk_widget_class_bind_template_child (widget_class, CcActionRow, button);
+
+  gtk_widget_class_bind_template_callback (widget_class, clicked_cb);
+}
+
+static void
+cc_action_row_init (CcActionRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcActionRow *
+cc_action_row_new (void)
+{
+  return CC_ACTION_ROW (g_object_new (CC_TYPE_ACTION_ROW, NULL));
+}
+
+void
+cc_action_row_set_title (CcActionRow *row,
+                         const gchar *name)
+{
+  gtk_label_set_label (GTK_LABEL (row->title), name);
+}
+
+void
+cc_action_row_set_subtitle (CcActionRow *row,
+                            const gchar *name)
+{
+  gtk_label_set_label (GTK_LABEL (row->subtitle), name);
+  gtk_widget_set_visible (row->subtitle, strlen (name) > 0);
+}
+
+void
+cc_action_row_set_action (CcActionRow *row,
+                          const gchar *action,
+                          gboolean     sensitive)
+{
+  gtk_button_set_label (GTK_BUTTON (row->button), action);
+  gtk_widget_set_sensitive (row->button, sensitive);
+}
diff --git a/panels/applications/cc-action-row.h b/panels/applications/cc-action-row.h
new file mode 100644
index 000000000..2912adbf9
--- /dev/null
+++ b/panels/applications/cc-action-row.h
@@ -0,0 +1,42 @@
+/* cc-action-row.h
+ *
+ * Copyright 2018 Matthias Clasen <matthias clasen gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_ACTION_ROW (cc_action_row_get_type())
+G_DECLARE_FINAL_TYPE (CcActionRow, cc_action_row, CC, ACTION_ROW, GtkListBoxRow)
+
+CcActionRow* cc_action_row_new          (void);
+
+void         cc_action_row_set_title    (CcActionRow *row,
+                                         const gchar *label);
+
+void         cc_action_row_set_subtitle (CcActionRow *row,
+                                         const gchar *label);
+
+void         cc_action_row_set_action   (CcActionRow *row,
+                                         const gchar *action,
+                                         gboolean     sensitive);
+
+G_END_DECLS
diff --git a/panels/applications/cc-action-row.ui b/panels/applications/cc-action-row.ui
new file mode 100644
index 000000000..54385a325
--- /dev/null
+++ b/panels/applications/cc-action-row.ui
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="CcActionRow" parent="GtkListBoxRow">
+    <property name="visible">True</property>
+    <property name="can-focus">True</property>
+    <property name="activatable">False</property>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">1</property>
+        <property name="border-width">12</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">1</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">4</property>
+            <child>
+              <object class="GtkLabel" id="title">
+                <property name="visible">1</property>
+                <property name="xalign">0</property>
+                <property name="hexpand">1</property>
+              </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>
+            <signal name="clicked" handler="clicked_cb" swapped="yes"/>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/panels/applications/cc-applications-panel.c b/panels/applications/cc-applications-panel.c
new file mode 100644
index 000000000..4d61155fe
--- /dev/null
+++ b/panels/applications/cc-applications-panel.c
@@ -0,0 +1,1703 @@
+/* cc-applications-panel.c
+ *
+ * Copyright 2018 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "cc-applications-panel"
+
+#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 "globs.h"
+#include "list-box-helper.h"
+#include "search.h"
+#include "utils.h"
+
+#define MASTER_SCHEMA "org.gnome.desktop.notifications"
+#define APP_SCHEMA MASTER_SCHEMA ".application"
+#define APP_PREFIX "/org/gnome/desktop/notifications/application/"
+
+struct _CcApplicationsPanel
+{
+  CcPanel          parent;
+
+  GtkListBox      *sidebar_listbox;
+  GtkWidget       *header_button;
+  GtkWidget       *title_label;
+  GAppInfoMonitor *monitor;
+  gulong           monitor_id;
+
+  GCancellable    *cancellable;
+
+  gchar           *current_app_id;
+  gchar           *current_flatpak_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;
+};
+
+static void select_app (CcApplicationsPanel *self,
+                        const gchar         *app_id);
+
+G_DEFINE_TYPE (CcApplicationsPanel, cc_applications_panel, CC_TYPE_PANEL)
+
+enum
+{
+  PROP_0,
+  PROP_PARAMETERS
+};
+
+/* Callbacks */
+
+static gboolean
+privacy_link_cb (CcApplicationsPanel *self)
+{
+  CcShell *shell = cc_panel_get_shell (CC_PANEL (self));
+  g_autoptr(GError) error = NULL;
+
+  if (!cc_shell_set_active_panel_from_id (shell, "privacy", NULL, &error))
+    g_warning ("Failed to switch to privacy panel: %s", error->message);
+
+  return TRUE;
+}
+
+static void
+open_software_cb (GtkButton           *button,
+                  CcApplicationsPanel *self)
+{
+  const gchar *argv[] = { "gnome-software", "--details", "appid", NULL };
+
+  if (self->current_app_id == NULL)
+    argv[1] = NULL;
+  else
+    argv[2] = self->current_app_id;
+
+  g_spawn_async (NULL, (char **)argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
+}
+
+/* --- flatpak permissions and utilities --- */
+
+static gchar **
+get_flatpak_permissions (CcApplicationsPanel *self,
+                         const gchar         *table,
+                         const gchar         *id,
+                         const gchar         *app_id)
+{
+  g_autoptr(GVariant) ret = NULL;
+  g_autoptr(GVariantIter) iter = NULL;
+  g_autoptr(GVariant) val = NULL;
+  g_autofree gchar *key = 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)
+        return g_variant_dup_strv (val, NULL); 
+    }
+
+  val = NULL; /* freed by g_variant_iter_loop */
+  key = NULL;
+
+  return NULL;
+}
+
+static void
+set_flatpak_permissions (CcApplicationsPanel *self,
+                         const gchar *table,
+                         const gchar *id,
+                         const gchar *app_id,
+                         const gchar * 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 char *
+get_flatpak_id (GAppInfo *info)
+{
+  if (G_IS_DESKTOP_APP_INFO (info))
+    return g_desktop_app_info_get_string (G_DESKTOP_APP_INFO (info), "X-Flatpak");
+
+  return NULL;
+}
+
+static GFile *
+get_flatpak_app_dir (const gchar *app_id,
+                     const gchar *subdir)
+{
+  g_autofree gchar *path = NULL;
+  g_autoptr(GFile) appdir = NULL;
+
+  path = g_build_filename (g_get_home_dir (), ".var", "app", app_id, NULL);
+  appdir = g_file_new_for_path (path);
+
+  return g_file_get_child (appdir, subdir);
+}
+
+/* --- search settings --- */
+
+static void
+set_search_enabled (CcApplicationsPanel *self,
+                    const gchar         *app_id,
+                    gboolean             enabled)
+{
+  g_autoptr(GPtrArray) new_apps = NULL;
+  g_autofree gchar *desktop_id = NULL;
+  g_auto(GStrv) apps = NULL;
+  gpointer key, value;
+  gboolean default_disabled;
+  gint i;
+
+  desktop_id = g_strconcat (app_id, ".desktop", NULL);
+
+  if (!g_hash_table_lookup_extended (self->search_providers, app_id, &key, &value))
+    {
+      g_warning ("Trying to configure search for a provider-less app - this shouldn't happen");
+      return;
+    }
+
+  default_disabled = GPOINTER_TO_INT (value);
+
+  new_apps = g_ptr_array_new_with_free_func (g_free);
+  if (default_disabled)
+    {
+      apps = g_settings_get_strv (self->search_settings, "enabled");
+      for (i = 0; apps[i]; i++)
+        {
+          if (strcmp (apps[i], desktop_id) != 0)
+            g_ptr_array_add (new_apps, g_strdup (apps[i]));
+        }
+      if (enabled)
+        g_ptr_array_add (new_apps, g_strdup (desktop_id));
+      g_ptr_array_add (new_apps, NULL);
+      g_settings_set_strv (self->search_settings, "enabled",  (const gchar * const *)new_apps->pdata);
+    }
+  else
+    {
+      apps = g_settings_get_strv (self->search_settings, "disabled");
+      for (i = 0; apps[i]; i++)
+        {
+          if (strcmp (apps[i], desktop_id) != 0)
+            g_ptr_array_add (new_apps, g_strdup (apps[i]));
+        }
+      if (!enabled)
+        g_ptr_array_add (new_apps, g_strdup (desktop_id));
+      g_ptr_array_add (new_apps, NULL);
+      g_settings_set_strv (self->search_settings, "disabled", (const gchar * const *)new_apps->pdata);
+    }
+}
+
+static gboolean
+search_contains_string_for_app (CcApplicationsPanel *self,
+                                const gchar         *app_id,
+                                const gchar         *setting)
+{
+  g_autofree gchar *desktop_id = NULL;
+  g_auto(GStrv) apps = NULL;
+
+  desktop_id = g_strconcat (app_id, ".desktop", NULL);
+  apps = g_settings_get_strv (self->search_settings, setting);
+
+  return g_strv_contains ((const gchar * const *)apps, desktop_id);
+}
+
+static gboolean
+search_enabled_for_app (CcApplicationsPanel *self,
+                        const gchar         *app_id)
+{
+  return search_contains_string_for_app (self, app_id, "enabled");
+}
+
+static gboolean
+search_disabled_for_app (CcApplicationsPanel *self,
+                         const gchar         *app_id)
+{
+  return search_contains_string_for_app (self, app_id, "disabled");
+}
+
+static void
+get_search_enabled (CcApplicationsPanel *self,
+                    const gchar         *app_id,
+                    gboolean            *set,
+                    gboolean            *enabled)
+{
+  gpointer key, value;
+
+  *enabled = FALSE;
+  *set = g_hash_table_lookup_extended (self->search_providers, app_id, &key, &value);
+  if (!*set)
+    return;
+
+  if (search_enabled_for_app (self, app_id))
+    *enabled = TRUE;
+  else if (search_disabled_for_app (self, app_id))
+    *enabled = FALSE;
+  else
+    *enabled = !GPOINTER_TO_INT (value); 
+}
+
+static void
+search_cb (CcApplicationsPanel *self)
+{
+  if (self->current_app_id)
+    set_search_enabled (self,
+                        self->current_app_id,
+                        cc_toggle_row_get_allowed (CC_TOGGLE_ROW (self->search)));
+}
+
+/* --- notification permissions (flatpaks and non-flatpak) --- */
+
+static void
+get_notification_allowed (CcApplicationsPanel *self,
+                          const gchar         *app_id,
+                          gboolean            *set,
+                          gboolean            *allowed)
+{
+  if (self->notification_settings)
+    {
+      /* FIXME */
+      *set = TRUE;
+      *allowed = g_settings_get_boolean (self->notification_settings, "enable");
+    }
+  else
+    {
+      g_auto(GStrv) perms = get_flatpak_permissions (self, "notifications", "notification", app_id);
+      *set = perms != NULL;
+      /* FIXME: needs unreleased xdg-desktop-portals to write permissions on use */
+      *set = TRUE;
+      *allowed = perms == NULL || strcmp (perms[0], "no") != 0;
+    }
+}
+
+static void
+set_notification_allowed (CcApplicationsPanel *self,
+                          gboolean             allowed)
+{
+  if (self->notification_settings)
+    {
+      g_settings_set_boolean (self->notification_settings, "enable", allowed);
+    }
+  else
+    {
+      const gchar *perms[2] = { NULL, NULL };
+
+      perms[0] = allowed ? "yes" : "no";
+      set_flatpak_permissions (self, "notifications", "notification", self->current_flatpak_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 gchar *
+munge_app_id (const gchar *app_id)
+{
+  gchar *id = g_strdup (app_id);
+  gint i;
+
+  g_strcanon (id,
+              "0123456789"
+              "abcdefghijklmnopqrstuvwxyz"
+              "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+              "-",
+              '-');
+  for (i = 0; id[i] != '\0'; i++)
+    id[i] = g_ascii_tolower (id[i]);
+
+  return id;
+}
+
+static GSettings *
+get_notification_settings (const gchar *app_id)
+{
+  g_autofree gchar *munged_app_id = munge_app_id (app_id);
+  g_autofree gchar *path = g_strconcat (APP_PREFIX, munged_app_id, "/", NULL);
+  return g_settings_new_with_path (APP_SCHEMA, path);
+}
+
+/* --- device (microphone, camera, speaker) permissions (flatpak) --- */
+
+static void
+get_device_allowed (CcApplicationsPanel *self,
+                    const gchar         *device,
+                    const gchar         *app_id,
+                    gboolean            *set,
+                    gboolean            *allowed)
+{
+  g_auto(GStrv) perms = NULL;
+
+  perms = get_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 gchar         *device,
+                    gboolean             allowed)
+{
+  const gchar *perms[2];
+
+  perms[0] = allowed ? "yes" : "no";
+  perms[1] = NULL;
+
+  set_flatpak_permissions (self, "devices", device, self->current_flatpak_id, perms);
+}
+
+static void
+microphone_cb (CcApplicationsPanel *self)
+{
+  if (self->current_flatpak_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_flatpak_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_flatpak_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 gchar         *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 gchar *perms[3];
+
+  /* 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 gint
+add_static_permission_row (CcApplicationsPanel *self,
+                           const gchar         *title,
+                           const gchar         *subtitle)
+{
+  GtkWidget *row;
+
+  row = g_object_new (CC_TYPE_INFO_ROW,
+                      "title", title,
+                      "info", subtitle,
+                      NULL);
+  gtk_container_add (GTK_CONTAINER (self->builtin_list), row);
+
+  return 1;
+}
+
+static void
+permission_row_activated_cb (GtkListBox          *list,
+                             GtkListBoxRow       *list_row,
+                             CcApplicationsPanel *self)
+{
+  GtkWidget *row = GTK_WIDGET (list_row);
+
+  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 gchar         *app_id)
+{
+  g_autoptr(GKeyFile) keyfile = NULL;
+  gchar **strv;
+  gchar *str;
+  gint added = 0;
+  g_autofree gchar *text = NULL;
+  
+  keyfile = get_flatpak_metadata (app_id);
+  if (keyfile == NULL)
+    return FALSE;
+
+  strv = g_key_file_get_string_list (keyfile, "Context", "sockets", NULL, NULL);
+  if (strv && g_strv_contains ((const gchar * const*)strv, "system-bus"))
+    added += add_static_permission_row (self, _("System Bus"), _("Full access"));
+  if (strv && g_strv_contains ((const gchar * 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 gchar * 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 gchar * 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 gchar * const *)strv, "home") ||
+               g_strv_contains ((const gchar * const *)strv, "home:rw")))
+    added += add_static_permission_row (self, _("Home"), _("Full access"));
+  else if (strv && g_strv_contains ((const gchar * const *)strv, "home:ro"))
+    added += add_static_permission_row (self, _("Home"), _("Read-only"));
+  if (strv && (g_strv_contains ((const gchar * const *)strv, "host") ||
+               g_strv_contains ((const gchar * const *)strv, "host:rw")))
+    added += add_static_permission_row (self, _("File System"), _("Full access"));
+  else if (strv && g_strv_contains ((const gchar * 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 gchar *flatpak_id = get_flatpak_id (info);
+  gboolean disabled, allowed, set;
+  gboolean has_any = FALSE;
+
+  if (flatpak_id == NULL)
+    {
+      gtk_widget_hide (self->permission_section);
+      return;
+    }
+
+  disabled = g_settings_get_boolean (self->privacy_settings, "disable-camera");
+  get_device_allowed (self, "camera", flatpak_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", flatpak_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, flatpak_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, flatpak_id);
+
+  gtk_widget_set_visible (self->permission_section, has_any);
+}
+
+/* --- gintegration section --- */
+
+static void
+update_integration_section (CcApplicationsPanel *self,
+                            GAppInfo            *info)
+{
+  g_autofree gchar *app_id = get_app_id (info);
+  g_autofree gchar *flatpak_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 (flatpak_id != NULL)
+    {
+      g_clear_object (&self->notification_settings);
+      get_notification_allowed (self, flatpak_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", flatpak_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 gchar *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 gchar *)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,
+                        gint        delta)
+{
+  gint count;
+  g_autofree gchar *text = NULL;
+
+  count = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "count"));
+  count += delta;
+  g_object_set_data (G_OBJECT (row), "count", GINT_TO_POINTER (count));
+  text = g_strdup_printf ("%d", count);
+  g_object_set (row, "info", text, NULL);
+}
+
+static void
+add_scheme (CcApplicationsPanel *self,
+            GtkWidget           *after,
+            const gchar         *type)
+{
+  CcActionRow *row = NULL;
+  gint pos;
+
+  if (g_str_has_suffix (type, "http"))
+    {
+      row = cc_action_row_new ();
+      cc_action_row_set_title (row, _("Web Links"));
+      cc_action_row_set_subtitle (row, "http://, https://";);
+    }
+  else if (g_str_has_suffix (type, "https"))
+    {
+      return; /* assume anything that handles https also handles http */
+    }
+  else if (g_str_has_suffix (type, "git"))
+    {
+      row = cc_action_row_new ();
+      cc_action_row_set_title (row, _("Git Links"));
+      cc_action_row_set_subtitle (row, "git://");
+    }
+  else
+    {
+      gchar *scheme = strrchr (type, '/') + 1;
+      g_autofree gchar *title = g_strdup_printf (_("%s Links"), scheme);
+      g_autofree gchar *subtitle = g_strdup_printf ("%s://", scheme);
+
+      row = cc_action_row_new ();
+      cc_action_row_set_title (row, title);
+      cc_action_row_set_subtitle (row, subtitle);
+    }
+
+  cc_action_row_set_action (row, _("Unset"), TRUE);
+  g_object_set_data_full (G_OBJECT (row), "type", g_strdup (type), g_free);
+  g_signal_connect_object (row,
+                           "activated",
+                           G_CALLBACK (unset_cb),
+                           self, 0);
+
+  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 gchar         *type)
+{
+  CcActionRow *row;
+  const gchar *desc;
+  gint pos;
+  const gchar *glob;
+
+  glob = g_hash_table_lookup (self->globs, type);
+
+  desc = g_content_type_get_description (type);
+  row = cc_action_row_new ();
+  cc_action_row_set_title (row, desc);
+  cc_action_row_set_subtitle (row, glob ? glob : "");
+  cc_action_row_set_action (row, _("Unset"), TRUE);
+  g_object_set_data_full (G_OBJECT (row), "type", g_strdup (type), g_free);
+  g_signal_connect (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 gchar *type)
+{
+  const gchar *types[] = {
+    "text/html",
+    "text/htmlh",
+    "text/xml",
+    "application/xhtml+xml",
+    "application/vnd.mozilla.xul+xml",
+    "text/mml",
+    NULL
+  };
+  return g_strv_contains (types, type);
+}
+
+static void
+ensure_group_row (CcApplicationsPanel *self,
+                  GtkWidget **row,
+                  const gchar *title)
+{
+  if (*row == NULL)
+    {
+      CcInfoRow *r = CC_INFO_ROW (g_object_new (CC_TYPE_INFO_ROW,
+                                                "title", title,
+                                                "has-expander", TRUE,
+                                                NULL));
+      gtk_list_box_insert (GTK_LIST_BOX (self->handler_list), GTK_WIDGET (r), -1);
+      *row = GTK_WIDGET (r);
+    }
+}
+
+static void
+add_link_type (CcApplicationsPanel *self,
+               const gchar         *type)
+{
+  ensure_group_row (self, &self->link, _("Links"));
+  add_scheme (self, self->link, type);
+}
+
+static void
+add_hypertext_type (CcApplicationsPanel *self,
+                    const gchar         *type)
+{
+  ensure_group_row (self, &self->hypertext, _("Hypertext Files"));
+  add_file_type (self, self->hypertext, type);
+}
+
+static gboolean
+is_text_type (const gchar *type)
+{
+  return g_content_type_is_a (type, "text/*");
+}
+
+static void
+add_text_type (CcApplicationsPanel *self,
+               const gchar         *type)
+{
+  ensure_group_row (self, &self->text, _("Text Files"));
+  add_file_type (self, self->text, type);
+}
+
+static gboolean
+is_image_type (const gchar *type)
+{
+  return g_content_type_is_a (type, "image/*");
+}
+
+static void
+add_image_type (CcApplicationsPanel *self,
+                const gchar         *type)
+{
+  ensure_group_row (self, &self->images, _("Image Files"));
+  add_file_type (self, self->images, type);
+}
+
+static gboolean
+is_font_type (const gchar *type)
+{
+  return g_content_type_is_a (type, "font/*") ||
+         g_str_equal (type, "application/x-font-pcf") ||
+         g_str_equal (type, "application/x-font-type1");
+}
+
+static void
+add_font_type (CcApplicationsPanel *self,
+               const gchar         *type)
+{
+  ensure_group_row (self, &self->fonts, _("Font Files"));
+  add_file_type (self, self->fonts, type);
+}
+
+static gboolean
+is_archive_type (const gchar *type)
+{
+  const gchar *types[] = {
+    "application/bzip2",
+    "application/zip",
+    "application/x-xz-compressed-tar",
+    "application/x-xz",
+    "application/x-xar",
+    "application/x-tarz",
+    "application/x-tar",
+    "application/x-lzma-compressed-tar",
+    "application/x-lzma",
+    "application/x-lzip-compressed-tar",
+    "application/x-lzip",
+    "application/x-lha",
+    "application/gzip",
+    "application/x-cpio",
+    "application/x-compressed-tar",
+    "application/x-compress",
+    "application/x-bzip-compressed-tar",
+    "application/x-bzip",
+    "application/x-7z-compressed-tar",
+    "application/x-7z-compressed",
+    "application/x-zoo",
+    "application/x-war",
+    "application/x-stuffit",
+    "application/x-rzip-compressed-tar",
+    "application/x-rzip",
+    "application/vnd.rar",
+    "application/x-lzop-compressed-tar",
+    "application/x-lzop",
+    "application/x-lz4-compressed-tar",
+    "application/x-lz4",
+    "application/x-lrzip-compressed-tar",
+    "application/x-lrzip",
+    "application/x-lhz",
+    "application/x-java-archive",
+    "application/x-ear",
+    "application/x-cabinet",
+    "application/x-bzip1-compressed-tar",
+    "application/x-bzip1",
+    "application/x-arj",
+    "application/x-archive",
+    "application/x-ar",
+    "application/x-alz",
+    "application/x-ace",
+    "application/vnd.ms-cab-compressed",
+    NULL
+  };
+  return g_strv_contains (types, type);
+}
+
+static void
+add_archive_type (CcApplicationsPanel *self,
+                  const gchar         *type)
+{
+  ensure_group_row (self, &self->archives, _("Archive Files"));
+  add_file_type (self, self->archives, type);
+}
+
+static gboolean
+is_package_type (const gchar *type)
+{
+  const gchar *types[] = {
+    "application/x-source-rpm",
+    "application/x-rpm",
+    "application/vnd.debian.binary-package",
+    NULL
+  };
+  return g_strv_contains (types, type);
+}
+
+static void
+add_package_type (CcApplicationsPanel *self,
+                  const gchar         *type)
+{
+  ensure_group_row (self, &self->packages, _("Package Files"));
+  add_file_type (self, self->packages, type);
+}
+
+static gboolean
+is_audio_type (const gchar *type)
+{
+  return g_content_type_is_a (type, "audio/*") ||
+         g_str_equal (type, "application/ogg") ||
+         g_str_equal (type, "application/x-shorten") ||
+         g_str_equal (type, "application/x-matroska") ||
+         g_str_equal (type, "application/x-flac") ||
+         g_str_equal (type, "application/x-extension-mp4") ||
+         g_str_equal (type, "application/x-extension-m4a") ||
+         g_str_equal (type, "application/vnd.rn-realmedia") ||
+         g_str_equal (type, "application/ram") ||
+         g_str_equal (type, "application/vnd.ms-wpl");
+}
+
+static void
+add_audio_type (CcApplicationsPanel *self,
+                const gchar         *type)
+{
+  ensure_group_row (self, &self->audio, _("Audio Files"));
+  add_file_type (self, self->audio, type);
+}
+
+static gboolean
+is_video_type (const gchar *type)
+{
+  return g_content_type_is_a (type, "video/*") ||
+         g_str_equal (type, "application/x-smil") ||
+         g_str_equal (type, "application/vnd.ms-asf") ||
+         g_str_equal (type, "application/mxf");
+}
+
+static void
+add_video_type (CcApplicationsPanel *self,
+                const gchar         *type)
+{
+  ensure_group_row (self, &self->video, _("Video Files"));
+  add_file_type (self, self->video, type);
+}
+
+static void
+add_other_type (CcApplicationsPanel *self,
+                const gchar         *type)
+{
+  ensure_group_row (self, &self->other, _("Other Files"));
+  add_file_type (self, self->other, type);
+}
+
+static void
+add_handler_row (CcApplicationsPanel *self,
+                 const gchar         *type)
+{
+  gtk_widget_show (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 gchar *type)
+{
+  /* this is horribly inefficient. I blame the mime system */
+  g_autolist(GObject) list = NULL;
+  GList *l;
+  gboolean ret = FALSE;
+
+  list = g_app_info_get_recommended_for_type (type);
+  for (l = list; l; l = l->next)
+    {
+      GAppInfo *ri = l->data;
+
+      if (g_app_info_equal (info, ri))
+        {
+          ret = TRUE;
+          break;
+        }
+    }
+
+  return ret;
+}
+
+static void
+handler_reset_cb (GtkButton           *button,
+                  CcApplicationsPanel *self)
+{
+  GtkListBoxRow *selected;
+  GAppInfo *info;
+  const gchar **types;
+  gint 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++)
+    {
+      gchar *ctype = g_content_type_from_mime_type (types[i]);
+      g_app_info_add_supports_type (info, ctype, NULL);
+    }
+  g_signal_handler_unblock (self->monitor, self->monitor_id);
+  g_signal_emit_by_name (self->monitor, "changed");
+}
+
+static void
+update_handler_sections (CcApplicationsPanel *self,
+                         GAppInfo            *info)
+{
+  g_autoptr(GHashTable) hash = NULL;
+  const gchar **types;
+  gint i;
+
+  container_remove_all (GTK_CONTAINER (self->handler_list));
+
+  self->hypertext = NULL;
+  self->text = NULL;
+  self->images = NULL;
+  self->fonts = NULL;
+  self->archives = NULL;
+  self->packages = NULL;
+  self->audio = NULL;
+  self->video = NULL;
+  self->other = NULL;
+  self->link = NULL;
+
+  gtk_widget_hide (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++)
+    {
+      gchar *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);
+
+  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)
+{
+  g_autofree gchar *formatted_size = NULL;
+  guint64 total;
+
+  total = self->app_size + self->data_size + self->cache_size;
+  formatted_size = g_format_size (total);
+  g_object_set (self->total, "info", formatted_size, NULL);
+  g_object_set (self->storage, "info", formatted_size, NULL);
+}
+
+static void
+set_cache_size (GObject      *source,
+                GAsyncResult *res,
+                gpointer      data)
+{
+  CcApplicationsPanel *self = data;
+  g_autofree gchar *formatted_size = NULL;
+  guint64 *size;
+
+  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 gchar         *app_id)
+{
+  g_autoptr(GFile) dir = get_flatpak_app_dir (app_id, "cache");
+  g_object_set (self->cache, "info", "...", NULL);
+  file_size_async (dir, set_cache_size, self);
+}
+
+static void
+set_data_size (GObject      *source,
+               GAsyncResult *res,
+               gpointer      data)
+{
+  CcApplicationsPanel *self = data;
+  g_autofree gchar *formatted_size = NULL;
+  guint64 *size;
+
+  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 gchar          *app_id)
+{
+  g_autoptr(GFile) dir = get_flatpak_app_dir (app_id, "data");
+
+  g_object_set (self->data, "info", "...", NULL);
+  file_size_async (dir, 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 gchar         *app_id)
+{
+  g_autofree gchar *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 gchar         *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)
+{
+  g_autofree gchar *flatpak_id = get_flatpak_id (info);
+
+  if (flatpak_id != NULL)
+    {
+      gtk_widget_show (self->usage_section);
+      update_flatpak_sizes (self, flatpak_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_message ("No permissions store proxy yet, come back later");
+      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);
+  g_clear_pointer (&self->current_flatpak_id, g_free);
+
+  update_permission_section (self, info);
+  update_integration_section (self, info);
+  update_handler_sections (self, info);
+  update_usage_section (self, info);
+
+  self->current_app_id = get_app_id (info);
+  self->current_flatpak_id = get_flatpak_id (info);
+}
+
+static void
+populate_applications (CcApplicationsPanel *self)
+{
+  g_autolist(GObject) infos = NULL;
+  GList *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 gchar *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));
+    }
+}
+
+static gint
+compare_rows (GtkListBoxRow *row1,
+              GtkListBoxRow *row2,
+              gpointer       data)
+{
+  const gchar *key1 = cc_applications_row_get_sort_key (CC_APPLICATIONS_ROW (row1));
+  const gchar *key2 = cc_applications_row_get_sort_key (CC_APPLICATIONS_ROW (row2));
+
+  return strcmp (key1, key2);
+}
+
+static 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 gchar         *app_id)
+{
+  g_autoptr(GList) children = NULL;
+  GList *l;
+
+  children = gtk_container_get_children (GTK_CONTAINER (self->sidebar_listbox));
+  for (l = children; l; l = l->next)
+    {
+      CcApplicationsRow *row = CC_APPLICATIONS_ROW (l->data);
+      GAppInfo *info = cc_applications_row_get_info (row);
+      if (g_str_has_prefix (g_app_info_get_id (info), app_id))
+        {
+          gtk_list_box_select_row (self->sidebar_listbox, GTK_LIST_BOX_ROW (row));
+          break;
+        }
+    }
+}
+
+static void
+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 = 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
+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)
+{
+  CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (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;
+}
+
+static void
+cc_applications_panel_class_init (CcApplicationsPanelClass *klass)
+{
+  CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = cc_applications_panel_dispose;
+  object_class->finalize = cc_applications_panel_finalize;
+  object_class->constructed = cc_applications_panel_constructed;
+  object_class->set_property = cc_applications_panel_set_property;
+
+  panel_class->get_sidebar_widget = cc_applications_panel_get_sidebar_widget;
+  panel_class->get_title_widget = cc_applications_panel_get_title_widget;
+
+  g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters");
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/control-center/applications/cc-applications-panel.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, app);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin_dialog);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin_label);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin_list);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, cache);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, camera);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, clear_cache_button);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, data);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, header_button);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_section);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_reset);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_list);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, integration_list);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, integration_section);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, location);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, microphone);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_camera);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_location);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_microphone);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_search);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_sound);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, notification);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, permission_section);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, permission_list);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sidebar_listbox);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, search);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sound);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, stack);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage_dialog);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage_list);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, title_label);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, total);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, usage_list);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, usage_section);
+
+  gtk_widget_class_bind_template_callback (widget_class, camera_cb);
+  gtk_widget_class_bind_template_callback (widget_class, location_cb);
+  gtk_widget_class_bind_template_callback (widget_class, microphone_cb);
+  gtk_widget_class_bind_template_callback (widget_class, search_cb);
+  gtk_widget_class_bind_template_callback (widget_class, notification_cb);
+  gtk_widget_class_bind_template_callback (widget_class, 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.h b/panels/applications/cc-applications-panel.h
new file mode 100644
index 000000000..c6c88825b
--- /dev/null
+++ b/panels/applications/cc-applications-panel.h
@@ -0,0 +1,30 @@
+/* cc-applications-panel.h
+ *
+ * Copyright 2018 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <shell/cc-panel.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_APPLICATIONS_PANEL (cc_applications_panel_get_type())
+G_DECLARE_FINAL_TYPE (CcApplicationsPanel, cc_applications_panel, CC, APPLICATIONS_PANEL, CcPanel)
+
+G_END_DECLS
diff --git a/panels/applications/cc-applications-panel.ui b/panels/applications/cc-applications-panel.ui
new file mode 100644
index 000000000..b4fbbfc79
--- /dev/null
+++ b/panels/applications/cc-applications-panel.ui
@@ -0,0 +1,531 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="CcApplicationsPanel" parent="CcPanel">
+    <property name="visible">True</property>
+    <property name="can-focus">False</property>
+    <child>
+      <object class="GtkScrolledWindow" id="main_scroll">
+        <property name="visible">1</property>
+        <property name="hscrollbar-policy">never</property>
+        <child>
+          <object class="HdyColumn">
+            <property name="visible">True</property>
+            <property name="maximum_width">600</property>
+            <property name="linear_growth_width">400</property>
+            <property name="margin_top">32</property>
+            <property name="margin_bottom">32</property>
+            <property name="margin_start">12</property>
+            <property name="margin_end">12</property>
+            <child>
+              <object class="GtkStack" id="stack">
+                <property name="visible">1</property>
+                <child>
+                  <object class="GtkBox">
+                    <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>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">24</property>
+                    <property name="hexpand">1</property>
+                    <child>
+                      <object class="GtkBox" id="permission_section">
+                        <property name="visible">1</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">12</property>
+                        <style>
+                          <class name="section"/>
+                        </style>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">1</property>
+                            <property name="orientation">vertical</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Permissions &amp; Access</property>
+                                <style>
+                                  <class name="section-title"/>
+                                </style>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">1</property>
+                                <property name="xalign">0</property>
+                                <property name="wrap">1</property>
+                                <property name="max-width-chars">50</property>
+                                <property name="label" translatable="yes">Data and services that this app 
has asked for access to and permissions that it requires.</property>
+                                <style>
+                                  <class name="section-subtitle"/>
+                                </style>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkListBox" id="permission_list">
+                            <property name="visible">1</property>
+                            <property name="selection-mode">none</property>
+                            <signal name="row-activated" handler="permission_row_activated_cb"/>
+                            <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>
+                  <packing>
+                    <property name="name">settings</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkLabel" id="title_label">
+    <property name="visible">1</property>
+    <property name="label" translatable="yes">Applications</property>
+    <style>
+      <class name="title"/>
+    </style>
+  </object>
+  <object class="GtkButton" id="header_button">
+    <property name="visible">1</property>
+    <property name="label" translatable="yes">Open in Software</property>
+  </object>
+  <object class="GtkListBox" id="sidebar_listbox">
+    <property name="visible">1</property>
+    <property name="selection-mode">browse</property>
+  </object>
+
+  <!-- Built-in Permissions dialog -->
+  <object class="GtkDialog" id="builtin_dialog">
+    <property name="title" translatable="yes">Built-in Permissions</property>
+    <property name="modal">1</property>
+    <property name="type-hint">dialog</property>
+    <property name="use-header-bar">1</property>
+    <property name="resizable">0</property>
+    <property name="border-width">24</property>
+    <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="visible">1</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkLabel" id="builtin_label">
+            <property name="visible">1</property>
+            <property name="wrap">1</property>
+            <property name="max-width-chars">50</property>
+            <property name="label">Yadda Yadda</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkListBox" id="builtin_list">
+            <property name="visible">1</property>
+            <property name="selection-mode">none</property>
+            <style>
+              <class name="view"/>
+              <class name="frame"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+
+  <!-- Storage dialog -->
+  <object class="GtkDialog" id="storage_dialog">
+    <property name="title" translatable="yes">Storage</property>
+    <property name="modal">1</property>
+    <property name="type-hint">dialog</property>
+    <property name="use-header-bar">1</property>
+    <property name="resizable">0</property>
+    <property name="border-width">24</property>
+    <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="visible">1</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkLabel">
+            <property name="visible">1</property>
+            <property name="wrap">1</property>
+            <property name="max-width-chars">50</property>
+            <property name="label" translatable="yes">How much disk space this application is occupying with 
app data and caches.</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkListBox" id="storage_list">
+            <property name="visible">1</property>
+            <property name="selection-mode">none</property>
+            <child>
+              <object class="CcInfoRow" id="app">
+                <property name="title" translatable="yes">Application</property>
+                <property name="info">Unknown</property>
+              </object>
+            </child>
+            <child>
+              <object class="CcInfoRow" id="data">
+                <property name="title" translatable="yes">Data</property>
+                <property name="info">Unknown</property>
+              </object>
+            </child>
+            <child>
+              <object class="CcInfoRow" id="cache">
+                <property name="title" translatable="yes">Cache</property>
+                <property name="info">Unknown</property>
+              </object>
+            </child>
+            <child>
+              <object class="CcInfoRow" id="total">
+                <property name="title" translatable="yes">&lt;b&gt;Total&lt;/b&gt;</property>
+                <property name="use-markup">1</property>
+                <property name="info">Unknown</property>
+              </object>
+            </child>
+            <style>
+              <class name="view"/>
+              <class name="frame"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">1</property>
+            <child>
+              <object class="GtkButton" id="clear_cache_button">
+                <property name="visible">1</property>
+                <property name="label" translatable="yes">Clear Cacheā€¦</property>
+                <signal name="clicked" handler="clear_cache_cb" 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..02a5208f9
--- /dev/null
+++ b/panels/applications/cc-applications-row.c
@@ -0,0 +1,102 @@
+/* cc-applications-row.c
+ *
+ * Copyright 2018 Matthias Clasen <matthias clasen gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include "cc-applications-row.h"
+#include "cc-applications-resources.h"
+
+struct _CcApplicationsRow
+{
+  GtkListBoxRow parent;
+
+  GAppInfo     *info;
+  gchar        *sortkey;
+
+  GtkWidget    *box;
+  GtkWidget    *image;
+  GtkWidget    *label;
+};
+
+G_DEFINE_TYPE (CcApplicationsRow, cc_applications_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+cc_applications_row_finalize (GObject *object)
+{
+  CcApplicationsRow *self = CC_APPLICATIONS_ROW (object);
+
+  g_object_unref (self->info);
+  g_free (self->sortkey);
+
+  G_OBJECT_CLASS (cc_applications_row_parent_class)->finalize (object);
+}
+
+static void
+cc_applications_row_class_init (CcApplicationsRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = cc_applications_row_finalize;
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/control-center/applications/cc-applications-row.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsRow, box);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsRow, image);
+  gtk_widget_class_bind_template_child (widget_class, CcApplicationsRow, label);
+}
+
+static void
+cc_applications_row_init (CcApplicationsRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcApplicationsRow *
+cc_applications_row_new (GAppInfo *info)
+{
+  CcApplicationsRow *self;
+  g_autofree gchar *key = NULL;
+
+  self = g_object_new (CC_TYPE_APPLICATIONS_ROW, NULL);
+
+  self->info = g_object_ref (info);
+
+  key = g_utf8_casefold (g_app_info_get_display_name (info), -1);
+  self->sortkey = g_utf8_collate_key (key, -1);
+
+  gtk_image_set_from_gicon (GTK_IMAGE (self->image), g_app_info_get_icon (info), GTK_ICON_SIZE_BUTTON);
+  gtk_label_set_label (GTK_LABEL (self->label), g_app_info_get_display_name (info));
+
+  return self;
+}
+
+GAppInfo *
+cc_applications_row_get_info (CcApplicationsRow *self)
+{
+  return self->info;
+}
+
+const gchar *
+cc_applications_row_get_sort_key (CcApplicationsRow *self)
+{
+  return self->sortkey;
+}
diff --git a/panels/applications/cc-applications-row.h b/panels/applications/cc-applications-row.h
new file mode 100644
index 000000000..3080751d1
--- /dev/null
+++ b/panels/applications/cc-applications-row.h
@@ -0,0 +1,36 @@
+/* cc-applications-row.h
+ *
+ * Copyright 2018 Matthias Clasen <matthias clasen gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_APPLICATIONS_ROW (cc_applications_row_get_type())
+G_DECLARE_FINAL_TYPE (CcApplicationsRow, cc_applications_row, CC, APPLICATIONS_ROW, GtkListBoxRow)
+
+CcApplicationsRow *cc_applications_row_new          (GAppInfo          *info);
+
+GAppInfo          *cc_applications_row_get_info     (CcApplicationsRow *row);
+
+const gchar       *cc_applications_row_get_sort_key (CcApplicationsRow *row);
+
+G_END_DECLS
diff --git a/panels/applications/cc-applications-row.ui b/panels/applications/cc-applications-row.ui
new file mode 100644
index 000000000..fb483a1b9
--- /dev/null
+++ b/panels/applications/cc-applications-row.ui
@@ -0,0 +1,30 @@
+<?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>
+            <property name="pixel-size">16</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>
+            <property name="ellipsize">end</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/panels/applications/cc-info-row.c b/panels/applications/cc-info-row.c
new file mode 100644
index 000000000..3a8d88a76
--- /dev/null
+++ b/panels/applications/cc-info-row.c
@@ -0,0 +1,215 @@
+/* cc-info-row.c
+ *
+ * Copyright 2018 Matthias Clasen <matthias clasen gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include "cc-info-row.h"
+#include "cc-applications-resources.h"
+
+struct _CcInfoRow
+{
+  GtkListBoxRow parent;
+
+  GtkWidget    *title;
+  GtkWidget    *info;
+  GtkWidget    *expander;
+
+  gboolean      expanded;
+  gboolean      link;
+};
+
+G_DEFINE_TYPE (CcInfoRow, cc_info_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum
+{
+  PROP_0,
+  PROP_TITLE,
+  PROP_USE_MARKUP,
+  PROP_INFO,
+  PROP_HAS_EXPANDER,
+  PROP_IS_LINK,
+  PROP_EXPANDED
+};
+
+static void
+cc_info_row_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  CcInfoRow *row = CC_INFO_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_TITLE:
+      g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->title)));
+      break;
+    case PROP_INFO:
+      g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->info)));
+      break;
+    case PROP_HAS_EXPANDER:
+      g_value_set_boolean (value, gtk_widget_get_visible (row->expander));
+      break;
+    case PROP_USE_MARKUP:
+      g_value_set_boolean (value, gtk_label_get_use_markup (GTK_LABEL (row->title)));
+      break;
+    case PROP_IS_LINK:
+      g_value_set_boolean (value, row->link);
+      break;
+    case PROP_EXPANDED:
+      g_value_set_boolean (value, cc_info_row_get_expanded (row));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+update_expander (CcInfoRow *row)
+{
+  if (row->link)
+    gtk_image_set_from_icon_name (GTK_IMAGE (row->expander), "go-next-symbolic", GTK_ICON_SIZE_BUTTON);
+  else if (row->expanded)
+    gtk_image_set_from_icon_name (GTK_IMAGE (row->expander), "pan-down-symbolic", GTK_ICON_SIZE_BUTTON);
+  else
+    gtk_image_set_from_icon_name (GTK_IMAGE (row->expander), "pan-end-symbolic", GTK_ICON_SIZE_BUTTON);
+}
+
+static void
+cc_info_row_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  CcInfoRow *row = CC_INFO_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_TITLE:
+      gtk_label_set_label (GTK_LABEL (row->title), g_value_get_string (value));
+      break;
+
+    case PROP_INFO:
+      gtk_label_set_label (GTK_LABEL (row->info), g_value_get_string (value));
+      break;
+
+    case PROP_HAS_EXPANDER:
+      gtk_widget_set_visible (row->expander, g_value_get_boolean (value));
+      gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), g_value_get_boolean (value));
+      break;
+
+    case PROP_USE_MARKUP:
+      gtk_label_set_use_markup (GTK_LABEL (row->title), g_value_get_boolean (value));
+      break;
+
+    case PROP_IS_LINK:
+      row->link = g_value_get_boolean (value);
+      update_expander (row);
+      break;
+
+    case PROP_EXPANDED:
+      cc_info_row_set_expanded (row, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+cc_info_row_class_init (CcInfoRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->get_property = cc_info_row_get_property;
+  object_class->set_property = cc_info_row_set_property;
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/control-center/applications/cc-info-row.ui");
+
+  g_object_class_install_property (object_class,
+                                   PROP_TITLE,
+                                   g_param_spec_string ("title", "title", "title",
+                                                        NULL, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_INFO,
+                                   g_param_spec_string ("info", "info", "info",
+                                                        NULL, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_USE_MARKUP,
+                                   g_param_spec_boolean ("use-markup", "use-markup", "use-markup",
+                                                         FALSE, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_HAS_EXPANDER,
+                                   g_param_spec_boolean ("has-expander", "has-expander", "has-expander",
+                                                         FALSE, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_EXPANDED,
+                                   g_param_spec_boolean ("expanded", "expanded", "expanded",
+                                                         FALSE, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_IS_LINK,
+                                   g_param_spec_boolean ("is-link", "is-link", "is-link",
+                                                         FALSE, G_PARAM_READWRITE));
+
+  gtk_widget_class_bind_template_child (widget_class, CcInfoRow, title);
+  gtk_widget_class_bind_template_child (widget_class, CcInfoRow, info);
+  gtk_widget_class_bind_template_child (widget_class, CcInfoRow, expander);
+}
+
+static void
+cc_info_row_init (CcInfoRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcInfoRow *
+cc_info_row_new (void)
+{
+  return CC_INFO_ROW (g_object_new (CC_TYPE_INFO_ROW, NULL));
+}
+
+gboolean
+cc_info_row_get_expanded (CcInfoRow *row)
+{
+  return row->expanded;
+}
+
+void
+cc_info_row_set_expanded (CcInfoRow *row,
+                          gboolean   expanded)
+{
+  if (row->expanded == expanded)
+    return;
+
+  row->expanded = expanded;
+  update_expander (row);
+
+  g_object_notify (G_OBJECT (row), "expanded");
+}
+
diff --git a/panels/applications/cc-info-row.h b/panels/applications/cc-info-row.h
new file mode 100644
index 000000000..57b9d4a9e
--- /dev/null
+++ b/panels/applications/cc-info-row.h
@@ -0,0 +1,37 @@
+/* cc-info-row.h
+ *
+ * Copyright 2018 Matthias Clasen <matthias clasen gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_INFO_ROW (cc_info_row_get_type())
+G_DECLARE_FINAL_TYPE (CcInfoRow, cc_info_row, CC, INFO_ROW, GtkListBoxRow)
+
+CcInfoRow* cc_info_row_new          (void);
+
+void       cc_info_row_set_expanded (CcInfoRow *row,
+                                     gboolean expanded);
+
+gboolean   cc_info_row_get_expanded (CcInfoRow *row);
+
+G_END_DECLS
diff --git a/panels/applications/cc-info-row.ui b/panels/applications/cc-info-row.ui
new file mode 100644
index 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..4895cc45d
--- /dev/null
+++ b/panels/applications/cc-toggle-row.c
@@ -0,0 +1,144 @@
+/* cc-toggle-row.c
+ *
+ * Copyright 2018 Matthias Clasen <matthias clasen gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include "cc-toggle-row.h"
+#include "cc-applications-resources.h"
+
+struct _CcToggleRow
+{
+  GtkListBoxRow parent;
+
+  GtkWidget    *title;
+  GtkWidget    *toggle;
+};
+
+G_DEFINE_TYPE (CcToggleRow, cc_toggle_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum
+{
+  PROP_0,
+  PROP_TITLE,
+  PROP_ALLOWED
+};
+
+static void
+changed_cb (CcToggleRow *row)
+{
+  g_object_notify (G_OBJECT (row), "allowed");
+}
+
+static void
+cc_toggle_row_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  CcToggleRow *row = CC_TOGGLE_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_TITLE:
+      g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->title)));
+      break;
+    case PROP_ALLOWED:
+      g_value_set_boolean (value, cc_toggle_row_get_allowed (row));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+cc_toggle_row_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  CcToggleRow *row = CC_TOGGLE_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_TITLE:
+      gtk_label_set_label (GTK_LABEL (row->title), g_value_get_string (value));
+      break;
+    case PROP_ALLOWED:
+      cc_toggle_row_set_allowed (row, g_value_get_boolean (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+cc_toggle_row_class_init (CcToggleRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->get_property = cc_toggle_row_get_property;
+  object_class->set_property = cc_toggle_row_set_property;
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/control-center/applications/cc-toggle-row.ui");
+
+  g_object_class_install_property (object_class,
+                                   PROP_TITLE,
+                                   g_param_spec_string ("title", "title", "title",
+                                                        NULL, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_ALLOWED,
+                                   g_param_spec_boolean ("allowed", "allowed", "allowed",
+                                                         FALSE, G_PARAM_READWRITE));
+
+  gtk_widget_class_bind_template_child (widget_class, CcToggleRow, title);
+  gtk_widget_class_bind_template_child (widget_class, CcToggleRow, toggle);
+
+  gtk_widget_class_bind_template_callback (widget_class, changed_cb);
+}
+
+static void
+cc_toggle_row_init (CcToggleRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+CcToggleRow *
+cc_toggle_row_new (void)
+{
+  return CC_TOGGLE_ROW (g_object_new (CC_TYPE_TOGGLE_ROW, NULL));
+}
+
+void
+cc_toggle_row_set_allowed (CcToggleRow *self,
+                           gboolean     allowed)
+{
+  gtk_switch_set_active (GTK_SWITCH (self->toggle), allowed);
+}
+
+gboolean
+cc_toggle_row_get_allowed (CcToggleRow *self)
+{
+  return gtk_switch_get_active (GTK_SWITCH (self->toggle));
+}
diff --git a/panels/applications/cc-toggle-row.h b/panels/applications/cc-toggle-row.h
new file mode 100644
index 000000000..cfc66b91e
--- /dev/null
+++ b/panels/applications/cc-toggle-row.h
@@ -0,0 +1,37 @@
+/* cc-toggle-row.h
+ *
+ * Copyright 2018 Matthias Clasen <matthias clasen gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_TOGGLE_ROW (cc_toggle_row_get_type())
+G_DECLARE_FINAL_TYPE (CcToggleRow, cc_toggle_row, CC, TOGGLE_ROW, GtkListBoxRow)
+
+CcToggleRow* cc_toggle_row_new         (void);
+
+void         cc_toggle_row_set_allowed (CcToggleRow *row,
+                                        gboolean     allowed);
+
+gboolean     cc_toggle_row_get_allowed (CcToggleRow *row);
+
+G_END_DECLS
diff --git a/panels/applications/cc-toggle-row.ui b/panels/applications/cc-toggle-row.ui
new file mode 100644
index 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..4d2c939e2
--- /dev/null
+++ b/panels/applications/globs.c
@@ -0,0 +1,62 @@
+/* globs.c
+ *
+ * Copyright 2018 Matthias Clasen <matthias clasen gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+
+#include "globs.h"
+
+/* parse mime/globs and return a string->string hash table */
+GHashTable *
+parse_globs (void)
+{
+  GHashTable *globs;
+  const gchar * const *dirs;
+  gint i;
+
+  globs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+  dirs = g_get_system_data_dirs ();
+
+  for (i = 0; dirs[i]; i++)
+    {
+      g_autofree gchar *file = g_build_filename (dirs[i], "mime", "globs", NULL);
+      g_autofree gchar *contents = NULL;
+
+      if (g_file_get_contents (file, &contents, NULL, NULL))
+        {
+          g_auto(GStrv) strv = NULL;
+          int i;
+
+          strv = g_strsplit (contents, "\n", 0);
+          for (i = 0; strv[i]; i++)
+            {
+              g_auto(GStrv) parts = NULL;
+
+              if (strv[i][0] == '#' || strv[i][0] == '\0')
+                continue;
+
+              parts = g_strsplit (strv[i], ":", 2);
+              g_hash_table_insert (globs, g_strdup (parts[0]), g_strdup (parts[1]));
+            }
+        }
+    }
+
+  return globs;
+}
diff --git a/panels/applications/globs.h b/panels/applications/globs.h
new file mode 100644
index 000000000..0a54588c0
--- /dev/null
+++ b/panels/applications/globs.h
@@ -0,0 +1,29 @@
+/* globs.h
+ *
+ * Copyright 2018 Matthias Clasen <matthias clasen gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+GHashTable* parse_globs (void);
+
+G_END_DECLS
diff --git a/panels/applications/gnome-applications-panel.desktop.in.in 
b/panels/applications/gnome-applications-panel.desktop.in.in
new file mode 100644
index 000000000..76ccda921
--- /dev/null
+++ b/panels/applications/gnome-applications-panel.desktop.in.in
@@ -0,0 +1,15 @@
+[Desktop Entry]
+Name=Applications
+Comment=Control various application permissions and settings
+Exec=gnome-control-center applications
+# FIXME
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=application-x-executable-symbolic
+Terminal=false
+Type=Application
+NoDisplay=true
+StartupNotify=true
+Categories=GNOME;GTK;Settings;DesktopSettings;X-GNOME-Settings-Panel;X-GNOME-AccountSettings;
+OnlyShowIn=GNOME;Unity;
+# Translators: Search terms to find the Privacy panel. Do NOT translate or localize the semicolons! The list 
MUST also end with a semicolon!
+Keywords=application;flatpak;permission;setting;
diff --git a/panels/applications/meson.build b/panels/applications/meson.build
new file mode 100644
index 000000000..49796a322
--- /dev/null
+++ b/panels/applications/meson.build
@@ -0,0 +1,49 @@
+panels_list += cappletname
+desktop = 'gnome-@0@-panel.desktop'.format(cappletname)
+
+desktop_in = configure_file(
+          input : desktop + '.in.in',
+         output : desktop + '.in',
+  configuration : desktop_conf
+)
+
+i18n.merge_file(
+       desktop,
+         type : 'desktop',
+        input : desktop_in,
+       output : desktop,
+       po_dir : po_dir,
+      install : true,
+  install_dir : control_center_desktopdir
+)
+
+sources = files(
+  'cc-applications-panel.c',
+  'cc-applications-row.c',
+  'cc-toggle-row.c',
+  'cc-info-row.c',
+  'cc-action-row.c',
+  'globs.c',
+  'search.c',
+  'utils.c',
+)
+
+resource_data = files('cc-applications-panel.ui')
+
+sources += gnome.compile_resources(
+  'cc-' + cappletname + '-resources',
+  cappletname + '.gresource.xml',
+        c_name : 'cc_' + cappletname,
+  dependencies : resource_data,
+        export : true
+)
+
+cflags += '-DGNOMELOCALEDIR="@0@"'.format(control_center_localedir)
+
+panels_libs += static_library(
+           cappletname,
+              sources : sources,
+  include_directories : [ top_inc, common_inc ],
+         dependencies : common_deps,
+               c_args : cflags
+)
diff --git a/panels/applications/search.c b/panels/applications/search.c
new file mode 100644
index 000000000..448918ba5
--- /dev/null
+++ b/panels/applications/search.c
@@ -0,0 +1,133 @@
+/* search.c
+ *
+ * Copyright 2018 Matthias Clasen <matthias clasen gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+
+#include "search.h"
+
+
+#define SHELL_PROVIDER_GROUP "Shell Search Provider"
+
+static void
+add_one_provider (GHashTable *search_providers,
+                  GFile      *file)
+{
+  g_autoptr(GKeyFile) keyfile = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *app_id = NULL;
+  g_autofree gchar *path = NULL;
+  gboolean default_disabled;
+
+  path = g_file_get_path (file);
+  keyfile = g_key_file_new ();
+  g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, &error);
+
+  if (error != NULL)
+    {
+      g_warning ("Error loading %s: %s - search provider will be ignored",
+                 path, error->message);
+      return;
+    }
+
+  if (!g_key_file_has_group (keyfile, SHELL_PROVIDER_GROUP))
+    {
+      g_debug ("Shell search provider group missing from '%s', ignoring", path);
+      return;
+    }
+
+  app_id = g_key_file_get_string (keyfile, SHELL_PROVIDER_GROUP, "DesktopId", &error);
+
+  if (error != NULL)
+    {
+      g_warning ("Unable to read desktop ID from %s: %s - search provider will be ignored",
+                 path, error->message);
+      return;
+    }
+
+  if (g_str_has_suffix (app_id, ".desktop"))
+    app_id[strlen (app_id) - strlen (".desktop")] = '\0';
+
+  default_disabled = g_key_file_get_boolean (keyfile, SHELL_PROVIDER_GROUP, "DefaultDisabled", NULL);
+
+  g_hash_table_insert (search_providers, g_strdup (app_id), GINT_TO_POINTER (default_disabled));
+}
+
+static void
+parse_search_providers_one_dir (GHashTable  *search_providers,
+                                const gchar *system_dir)
+{
+  g_autoptr(GFileEnumerator) enumerator = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GFile) providers_location = NULL;
+  g_autofree gchar *providers_path = NULL;
+
+  providers_path = g_build_filename (system_dir, "gnome-shell", "search-providers", NULL);
+  providers_location = g_file_new_for_path (providers_path);
+
+  enumerator = g_file_enumerate_children (providers_location,
+                                          "standard::type,standard::name,standard::content-type",
+                                          G_FILE_QUERY_INFO_NONE,
+                                          NULL, &error);
+
+  if (error != NULL)
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
+          !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        g_warning ("Error opening %s: %s - search provider configuration won't be possible",
+                   providers_path, error->message);
+      return;
+    }
+
+  while (TRUE)
+    {
+      GFile *provider = NULL;
+
+      if (!g_file_enumerator_iterate (enumerator, NULL, &provider, NULL, &error))
+        {
+          g_warning ("Error while reading %s: %s - search provider configuration won't be possible",
+                   providers_path, error->message);
+          return;
+        }
+
+      if (provider == NULL)
+        break;
+
+      add_one_provider (search_providers, provider);
+    }
+}
+
+/* parse gnome-shell/search-provider files and return a string->boolean hash table */
+GHashTable *
+parse_search_providers (void)
+{
+  GHashTable *search_providers;
+  const gchar * const *dirs;
+  gint i;
+
+  search_providers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+  dirs = g_get_system_data_dirs ();
+
+  for (i = 0; dirs[i]; i++)
+    parse_search_providers_one_dir (search_providers, dirs[i]);
+
+  return search_providers;
+}
+
diff --git a/panels/applications/search.h b/panels/applications/search.h
new file mode 100644
index 000000000..b7ade8240
--- /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..0f8cc2976
--- /dev/null
+++ b/panels/applications/utils.c
@@ -0,0 +1,209 @@
+/* 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 <sys/types.h>
+#include <sys/stat.h>
+
+#include <ftw.h>
+
+#include "utils.h"
+
+static gint
+ftw_remove_cb (const gchar       *path,
+               const struct stat *sb,
+               gint               typeflags,
+               struct FTW        *ftwbuf)
+{
+  remove (path);
+  return 0;
+}
+
+static void
+file_remove_thread_func (GTask       *task,
+                         gpointer      source_object,
+                         gpointer      task_data,
+                         GCancellable *cancellable)
+{
+  GFile *file = source_object;
+  g_autofree gchar *path = g_file_get_path (file);
+
+  nftw (path, ftw_remove_cb, 20, FTW_DEPTH);
+}
+
+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 gint
+ftw_size_cb (const gchar       *path,
+             const struct stat *sb,
+             gint               typeflags,
+             struct FTW        *ftwbuf)
+{
+  guint64 *size = (guint64*)g_private_get (&size_key);
+  if (typeflags == FTW_F)
+    *size += sb->st_size;
+  return 0;
+}
+
+static void
+file_size_thread_func (GTask        *task,
+                       gpointer      source_object,
+                       gpointer      task_data,
+                       GCancellable *cancellable)
+{
+  GFile *file = source_object;
+  g_autofree gchar *path = g_file_get_path (file);
+  guint64 *total;
+
+  g_private_replace (&size_key, g_new0 (guint64, 1));
+
+  nftw (path, ftw_size_cb, 20, FTW_DEPTH);
+
+  total = g_new0 (guint64, 1);
+  *total = *(guint64*)g_private_get (&size_key);
+
+  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)
+{
+  g_autoptr(GList) children = NULL;
+  GList *l;
+
+  children = gtk_container_get_children (container);
+  for (l = children; l; l = l->next)
+    gtk_widget_destroy (GTK_WIDGET (l->data));
+}
+
+static gchar *
+get_output_of (const gchar **argv)
+{
+  g_autofree gchar *output = NULL;
+  int status;
+
+  if (!g_spawn_sync (NULL,
+                     (gchar**) argv,
+                     NULL,
+                     G_SPAWN_SEARCH_PATH,
+                     NULL, NULL,
+                     &output, NULL,
+                     &status, NULL))
+    return NULL;
+
+  if (!g_spawn_check_exit_status (status, NULL))
+    return NULL;
+
+  return g_steal_pointer (&output);
+}
+
+GKeyFile *
+get_flatpak_metadata (const gchar *app_id)
+{
+  const gchar *argv[5] = { "flatpak", "info", "-m", "app", NULL };
+  g_autofree gchar *data = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GKeyFile) keyfile = NULL;
+
+  argv[3] = app_id;
+
+  data = get_output_of (argv);
+  if (data == NULL)
+    return NULL;
+
+  keyfile = g_key_file_new ();
+  if (!g_key_file_load_from_data (keyfile, data, -1, 0, &error))
+    {
+      g_warning ("%s", error->message);
+      return NULL;
+    }
+
+  return g_steal_pointer (&keyfile);
+}
+
+guint64
+get_flatpak_app_size (const gchar *app_id)
+{
+  const gchar *argv[5] = { "flatpak", "info", "-s", "app", NULL };
+  g_autofree gchar *data = NULL;
+  guint64 factor;
+  double val;
+
+  argv[3] = app_id;
+
+  data = get_output_of (argv);
+  if (data == NULL)
+    return 0;
+
+  data = g_strstrip (data);
+  
+  if (g_str_has_suffix (data, "kB") || g_str_has_suffix (data, "kb"))
+    factor = 1000;
+  else if (g_str_has_suffix (data, "MB") || g_str_has_suffix (data, "Mb"))
+    factor = 1000 * 1000;
+  else if (g_str_has_suffix (data, "GB") || g_str_has_suffix (data, "Gb"))
+    factor = 1000 * 1000 * 1000;
+  else if (g_str_has_suffix (data, "KiB") || g_str_has_suffix (data, "Kib"))
+    factor = 1024;
+  else if (g_str_has_suffix (data, "MiB") || g_str_has_suffix (data, "Mib"))
+    factor = 1024 * 1024;
+  else if (g_str_has_suffix (data, "GiB") || g_str_has_suffix (data, "Gib"))
+    factor = 1024 * 1024 * 1024;
+  else 
+    factor = 1;
+
+  val = g_ascii_strtod (data, NULL);
+
+  return (guint64)(val * factor);
+}
+
+char *
+get_app_id (GAppInfo *info)
+{
+  gchar *app_id = g_strdup (g_app_info_get_id (info));
+
+  if (g_str_has_suffix (app_id, ".desktop"))
+    app_id[strlen (app_id) - strlen (".desktop")] = '\0';
+
+  return app_id;
+}
diff --git a/panels/applications/utils.h b/panels/applications/utils.h
new file mode 100644
index 000000000..557e455b6
--- /dev/null
+++ b/panels/applications/utils.h
@@ -0,0 +1,44 @@
+/* utils.h
+ *
+ * Copyright 2018 Matthias Clasen <matthias clasen gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void      file_remove_async    (GFile               *file,
+                                GAsyncReadyCallback  callback,
+                                gpointer             data);
+
+void      file_size_async      (GFile               *file,
+                                GAsyncReadyCallback  callback,
+                                gpointer             data);
+
+void      container_remove_all (GtkContainer        *container);
+
+GKeyFile* get_flatpak_metadata (const gchar         *app_id);
+
+guint64   get_flatpak_app_size (const gchar         *app_id);
+
+gchar*    get_app_id           (GAppInfo            *info);
+
+G_END_DECLS
diff --git a/panels/meson.build b/panels/meson.build
index 37a343642..95a44bb9d 100644
--- a/panels/meson.build
+++ b/panels/meson.build
@@ -1,6 +1,7 @@
 subdir('common')
 
 panels = [
+  'applications',
   'background',
   'color',
   'datetime',
diff --git a/shell/cc-panel-list.c b/shell/cc-panel-list.c
index 17899d44e..5bdc4c899 100644
--- a/shell/cc-panel-list.c
+++ b/shell/cc-panel-list.c
@@ -386,6 +386,7 @@ static const gchar * const panel_order[] = {
   "universal-access",
   "online-accounts",
   "privacy",
+  "applications",
   "sharing",
   "sound",
   "power",
diff --git a/shell/cc-panel-loader.c b/shell/cc-panel-loader.c
index 2798735c4..5eeb44e60 100644
--- a/shell/cc-panel-loader.c
+++ b/shell/cc-panel-loader.c
@@ -31,6 +31,7 @@
 #ifndef CC_PANEL_LOADER_NO_GTYPES
 
 /* Extension points */
+extern GType cc_applications_panel_get_type (void);
 extern GType cc_background_panel_get_type (void);
 #ifdef BUILD_BLUETOOTH
 extern GType cc_bluetooth_panel_get_type (void);
@@ -83,6 +84,7 @@ extern void cc_wacom_panel_static_init_func (void);
 
 static CcPanelLoaderVtable default_panels[] =
 {
+  PANEL_TYPE("applications",     cc_applications_panel_get_type,         NULL),
   PANEL_TYPE("background",       cc_background_panel_get_type,           NULL),
 #ifdef BUILD_BLUETOOTH
   PANEL_TYPE("bluetooth",        cc_bluetooth_panel_get_type,            NULL),



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