[gnome-software/wip/kalev/codecs: 9/10] Add a new Extras page for displaying PK session service results



commit 53d53055d06437d26bcf75ff9927326510fded1a
Author: Kalev Lember <kalevlember gmail com>
Date:   Mon Feb 9 17:12:33 2015 +0100

    Add a new Extras page for displaying PK session service results
    
    ... and implement the missing bits of the PK session service dbus
    interface.
    
    XXX: Go over translatable text before pushing.

 po/POTFILES.in                     |    2 +
 src/Makefile.am                    |    3 +
 src/gnome-software.gresource.xml   |    1 +
 src/gnome-software.ui              |    8 +
 src/gs-app-row.c                   |    9 +-
 src/gs-app-row.h                   |    2 +
 src/gs-application.c               |  192 ++++++
 src/gs-dbus-helper.c               |  429 ++++++++++++++
 src/gs-plugin-loader.c             |  334 +++++++++++
 src/gs-plugin-loader.h             |   18 +
 src/gs-plugin.h                    |   10 +
 src/gs-shell-extras.c              | 1124 ++++++++++++++++++++++++++++++++++++
 src/gs-shell-extras.h              |   86 +++
 src/gs-shell-extras.ui             |  145 +++++
 src/gs-shell.c                     |   95 +++-
 src/gs-shell.h                     |   22 +
 src/plugins/gs-plugin-packagekit.c |   62 ++
 17 files changed, 2540 insertions(+), 2 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2d6edc4..2400b18 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -21,6 +21,8 @@ src/gs-plugin-loader.c
 src/gs-popular-tile.c
 src/gs-screenshot-image.c
 src/gs-shell.c
+src/gs-shell-extras.c
+[type: gettext/glade]src/gs-shell-extras.ui
 src/gs-shell-details.c
 [type: gettext/glade]src/gs-shell-details.ui
 src/gs-shell-installed.c
diff --git a/src/Makefile.am b/src/Makefile.am
index b27988b..662efd1 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -37,6 +37,7 @@ UI_FILES =                                            \
        gs-first-run-dialog.ui                          \
        gs-history-dialog.ui                            \
        gs-shell-category.ui                            \
+       gs-shell-extras.ui                              \
        gs-shell-details.ui                             \
        gs-shell-installed.ui                           \
        gs-shell-overview.ui                            \
@@ -139,6 +140,8 @@ gnome_software_SOURCES =                            \
        gs-shell-details.h                              \
        gs-shell-category.c                             \
        gs-shell-category.h                             \
+       gs-shell-extras.c                               \
+       gs-shell-extras.h                               \
        gs-shell-installed.c                            \
        gs-shell-installed.h                            \
        gs-shell-overview.c                             \
diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml
index faf6d36..697abf5 100644
--- a/src/gnome-software.gresource.xml
+++ b/src/gnome-software.gresource.xml
@@ -14,6 +14,7 @@
   <file preprocess="xml-stripblanks">gs-first-run-dialog.ui</file>
   <file preprocess="xml-stripblanks">gs-history-dialog.ui</file>
   <file preprocess="xml-stripblanks">gs-shell-category.ui</file>
+  <file preprocess="xml-stripblanks">gs-shell-extras.ui</file>
   <file preprocess="xml-stripblanks">gs-shell-details.ui</file>
   <file preprocess="xml-stripblanks">gs-shell-installed.ui</file>
   <file preprocess="xml-stripblanks">gs-shell-overview.ui</file>
diff --git a/src/gnome-software.ui b/src/gnome-software.ui
index afd9d3a..5fbb8b5 100644
--- a/src/gnome-software.ui
+++ b/src/gnome-software.ui
@@ -441,6 +441,14 @@
                 <property name="name">category</property>
               </packing>
             </child>
+            <child>
+              <object class="GsShellExtras" id="shell_extras">
+                <property name="visible">True</property>
+              </object>
+              <packing>
+                <property name="name">extras</property>
+              </packing>
+            </child>
           </object>
           <packing>
             <property name="expand">True</property>
diff --git a/src/gs-app-row.c b/src/gs-app-row.c
index dc63619..2e9d6fd 100644
--- a/src/gs-app-row.c
+++ b/src/gs-app-row.c
@@ -48,6 +48,7 @@ struct _GsAppRowPrivate
        GtkWidget       *label;
        GtkWidget       *checkbox;
        gboolean         colorful;
+       gboolean         show_codec;
        gboolean         show_update;
        gboolean         selectable;
        guint            pending_refresh_id;
@@ -157,7 +158,7 @@ gs_app_row_refresh (GsAppRow *app_row)
                                     gs_app_get_version_ui (priv->app));
        }
 
-       if (priv->show_update) {
+       if (priv->show_update || priv->show_codec) {
                gtk_widget_hide (priv->folder_label);
        } else {
                _cleanup_object_unref_ GsFolders *folders = NULL;
@@ -489,6 +490,12 @@ gs_app_row_set_colorful (GsAppRow *app_row,
        app_row->priv->colorful = colorful;
 }
 
+void
+gs_app_row_set_show_codec (GsAppRow *app_row, gboolean show_codec)
+{
+       app_row->priv->show_codec = show_codec;
+}
+
 /**
  * gs_app_row_set_show_update:
  *
diff --git a/src/gs-app-row.h b/src/gs-app-row.h
index 41cf825..274f9ad 100644
--- a/src/gs-app-row.h
+++ b/src/gs-app-row.h
@@ -60,6 +60,8 @@ void           gs_app_row_refresh                     (GsAppRow       *app_row);
 void            gs_app_row_unreveal                    (GsAppRow       *app_row);
 void            gs_app_row_set_colorful                (GsAppRow       *app_row,
                                                         gboolean        colorful);
+void            gs_app_row_set_show_codec              (GsAppRow       *app_row,
+                                                        gboolean        show_codec);
 void            gs_app_row_set_show_update             (GsAppRow       *app_row,
                                                         gboolean        show_update);
 void            gs_app_row_set_selectable              (GsAppRow       *app_row,
diff --git a/src/gs-application.c b/src/gs-application.c
index b2077fd..e9b5142 100644
--- a/src/gs-application.c
+++ b/src/gs-application.c
@@ -442,6 +442,190 @@ show_offline_updates_error (GSimpleAction *action,
        gs_offline_updates_show_error ();
 }
 
+static void
+install_package_files_activated (GSimpleAction *action,
+                              GVariant      *parameter,
+                              gpointer       data)
+{
+       GsApplication *app = GS_APPLICATION (data);
+       GList *windows;
+       GtkWindow *window = NULL;
+       gchar **files;
+       guint32 timestamp;
+
+       g_variant_get (parameter, "(^asu)", &files, &timestamp);
+
+       windows = gtk_application_get_windows (GTK_APPLICATION (app));
+       if (windows) {
+               window = windows->data;
+               gtk_window_present_with_time (window, timestamp);
+       }
+       gs_application_initialize_ui (app);
+
+       gs_shell_show_extras_search_package_files (app->shell, files);
+}
+
+static void
+install_provide_files_activated (GSimpleAction *action,
+                              GVariant      *parameter,
+                              gpointer       data)
+{
+       GsApplication *app = GS_APPLICATION (data);
+       GList *windows;
+       GtkWindow *window = NULL;
+       gchar **files;
+       guint32 timestamp;
+
+       g_variant_get (parameter, "(^asu)", &files, &timestamp);
+
+       windows = gtk_application_get_windows (GTK_APPLICATION (app));
+       if (windows) {
+               window = windows->data;
+               gtk_window_present_with_time (window, timestamp);
+       }
+       gs_application_initialize_ui (app);
+
+       gs_shell_show_extras_search_provide_files (app->shell, files);
+}
+
+static void
+install_package_names_activated (GSimpleAction *action,
+                              GVariant      *parameter,
+                              gpointer       data)
+{
+       GsApplication *app = GS_APPLICATION (data);
+       GList *windows;
+       GtkWindow *window = NULL;
+       gchar **package_names;
+       guint32 timestamp;
+
+       g_variant_get (parameter, "(^asu)", &package_names, &timestamp);
+
+       windows = gtk_application_get_windows (GTK_APPLICATION (app));
+       if (windows) {
+               window = windows->data;
+               gtk_window_present_with_time (window, timestamp);
+       }
+       gs_application_initialize_ui (app);
+
+       gs_shell_show_extras_search_package_names (app->shell, package_names);
+}
+
+static void
+install_mime_types_activated (GSimpleAction *action,
+                              GVariant      *parameter,
+                              gpointer       data)
+{
+       GsApplication *app = GS_APPLICATION (data);
+       GList *windows;
+       GtkWindow *window = NULL;
+       gchar **mime_types;
+       guint32 timestamp;
+
+       g_variant_get (parameter, "(^asu)", &mime_types, &timestamp);
+
+       windows = gtk_application_get_windows (GTK_APPLICATION (app));
+       if (windows) {
+               window = windows->data;
+               gtk_window_present_with_time (window, timestamp);
+       }
+       gs_application_initialize_ui (app);
+
+       gs_shell_show_extras_search_mime_types (app->shell, mime_types);
+}
+
+static void
+install_fontconfig_resources_activated (GSimpleAction *action,
+                                   GVariant      *parameter,
+                                   gpointer       data)
+{
+       GsApplication *app = GS_APPLICATION (data);
+       GList *windows;
+       GtkWindow *window = NULL;
+       gchar **resources;
+       guint32 timestamp;
+
+       g_variant_get (parameter, "(^asu)", &resources, &timestamp);
+
+       windows = gtk_application_get_windows (GTK_APPLICATION (app));
+       if (windows) {
+               window = windows->data;
+               gtk_window_present_with_time (window, timestamp);
+       }
+       gs_application_initialize_ui (app);
+
+       gs_shell_show_extras_search_fontconfig_resources (app->shell, resources);
+}
+
+static void
+install_gstreamer_resources_activated (GSimpleAction *action,
+                                   GVariant      *parameter,
+                                   gpointer       data)
+{
+       GsApplication *app = GS_APPLICATION (data);
+       GList *windows;
+       GtkWindow *window = NULL;
+       gchar **resources;
+       guint32 timestamp;
+
+       g_variant_get (parameter, "(^asu)", &resources, &timestamp);
+
+       windows = gtk_application_get_windows (GTK_APPLICATION (app));
+       if (windows) {
+               window = windows->data;
+               gtk_window_present_with_time (window, timestamp);
+       }
+       gs_application_initialize_ui (app);
+
+       gs_shell_show_extras_search_gstreamer_resources (app->shell, resources);
+}
+
+static void
+install_plasma_resources_activated (GSimpleAction *action,
+                                   GVariant      *parameter,
+                                   gpointer       data)
+{
+       GsApplication *app = GS_APPLICATION (data);
+       GList *windows;
+       GtkWindow *window = NULL;
+       gchar **resources;
+       guint32 timestamp;
+
+       g_variant_get (parameter, "(^asu)", &resources, &timestamp);
+
+       windows = gtk_application_get_windows (GTK_APPLICATION (app));
+       if (windows) {
+               window = windows->data;
+               gtk_window_present_with_time (window, timestamp);
+       }
+       gs_application_initialize_ui (app);
+
+       gs_shell_show_extras_search_plasma_resources (app->shell, resources);
+}
+
+static void
+install_printer_drivers_activated (GSimpleAction *action,
+                              GVariant      *parameter,
+                              gpointer       data)
+{
+       GsApplication *app = GS_APPLICATION (data);
+       GList *windows;
+       GtkWindow *window = NULL;
+       gchar **device_ids;
+       guint32 timestamp;
+
+       g_variant_get (parameter, "(^asu)", &device_ids, &timestamp);
+
+       windows = gtk_application_get_windows (GTK_APPLICATION (app));
+       if (windows) {
+               window = windows->data;
+               gtk_window_present_with_time (window, timestamp);
+       }
+       gs_application_initialize_ui (app);
+
+       gs_shell_show_extras_search_printer_drivers (app->shell, device_ids);
+}
+
 static GActionEntry actions[] = {
        { "about", about_activated, NULL, NULL, NULL },
        { "sources", sources_activated, NULL, NULL, NULL },
@@ -454,6 +638,14 @@ static GActionEntry actions[] = {
        { "launch", launch_activated, "s", NULL, NULL },
        { "clear-offline-updates", clear_offline_updates, NULL, NULL, NULL },
        { "show-offline-update-error", show_offline_updates_error, NULL, NULL, NULL },
+       { "install-package-files", install_package_files_activated, "(asu)", NULL, NULL },
+       { "install-provide-files", install_provide_files_activated, "(asu)", NULL, NULL },
+       { "install-package-names", install_package_names_activated, "(asu)", NULL, NULL },
+       { "install-mime-types", install_mime_types_activated, "(asu)", NULL, NULL },
+       { "install-fontconfig-resources", install_fontconfig_resources_activated, "(asu)", NULL, NULL },
+       { "install-gstreamer-resources", install_gstreamer_resources_activated, "(asu)", NULL, NULL },
+       { "install-plasma-resources", install_plasma_resources_activated, "(asu)", NULL, NULL },
+       { "install-printer-drivers", install_printer_drivers_activated, "(asu)", NULL, NULL },
        { "nop", NULL, NULL, NULL }
 };
 
diff --git a/src/gs-dbus-helper.c b/src/gs-dbus-helper.c
index a65811e..d593222 100644
--- a/src/gs-dbus-helper.c
+++ b/src/gs-dbus-helper.c
@@ -21,7 +21,9 @@
 
 #include "config.h"
 
+#include <gio/gdesktopappinfo.h>
 #include <gio/gio.h>
+#include <glib/gi18n.h>
 #include <gtk/gtk.h>
 #include <packagekit-glib2/packagekit.h>
 
@@ -34,6 +36,7 @@ struct _GsDbusHelper {
        GObject                  parent;
        GCancellable            *cancellable;
        GDBusInterfaceSkeleton  *query_interface;
+       GDBusInterfaceSkeleton  *modify_interface;
        PkTask                  *task;
        guint                    dbus_own_name_id;
 };
@@ -269,6 +272,398 @@ handle_query_is_installed (GsPackageKitQuery       *skeleton,
 }
 
 static void
+notify_find_package_files (const gchar  *desktop_id,
+                           gchar       **files)
+{
+       const gchar *app_name = NULL;
+       _cleanup_free_ gchar *body = NULL;
+       _cleanup_object_unref_ GDesktopAppInfo *app_info = NULL;
+       _cleanup_object_unref_ GNotification *notification = NULL;
+
+       if (desktop_id != NULL) {
+               app_info = g_desktop_app_info_new (desktop_id);
+               if (app_info != NULL)
+                       app_name = g_app_info_get_name (G_APP_INFO (app_info));
+       }
+
+       if (app_name != NULL) {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an app wants to install 
additional packages. */
+               body = g_strdup_printf (_("%s is requesting additional packages."), app_name);
+       } else {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an unknown app wants to 
install additional packages. */
+               body = g_strdup (_("An application is requesting additional packages."));
+       }
+
+       /* TRANSLATORS: notification title */
+       notification = g_notification_new (_("Additional Packages Required"));
+       g_notification_set_body (notification, body);
+       /* TRANSLATORS: this is a button that launches gnome-software */
+       g_notification_add_button_with_target (notification, _("Find in Software"), 
"app.install-package-files", "(^asu)", files, 0);
+       g_notification_set_default_action_and_target (notification, "app.install-package-files", "(^asu)", 
files, 0);
+       g_application_send_notification (g_application_get_default (), "install-package-files", notification);
+}
+
+static gboolean
+handle_modify_install_package_files (GsPackageKitModify                 *object,
+                                     GDBusMethodInvocation      *invocation,
+                                     guint                       arg_xid,
+                                     gchar                     **arg_files,
+                                     const gchar                *arg_interaction,
+                                     gpointer                    user_data)
+{
+       g_debug ("****** Modify.InstallPackageFiles");
+
+       notify_find_package_files (NULL, arg_files);
+       gs_package_kit_modify_complete_install_package_files (object, invocation);
+
+       return TRUE;
+}
+
+static void
+notify_find_provide_files (const gchar  *desktop_id,
+                        gchar       **files)
+{
+       const gchar *app_name = NULL;
+       _cleanup_free_ gchar *body = NULL;
+       _cleanup_object_unref_ GDesktopAppInfo *app_info = NULL;
+       _cleanup_object_unref_ GNotification *notification = NULL;
+
+       if (desktop_id != NULL) {
+               app_info = g_desktop_app_info_new (desktop_id);
+               if (app_info != NULL)
+                       app_name = g_app_info_get_name (G_APP_INFO (app_info));
+       }
+
+       if (app_name != NULL) {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an app wants to install 
additional packages. */
+               body = g_strdup_printf (_("%s is requesting additional packages."), app_name);
+       } else {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an unknown app wants to 
install additional packages. */
+               body = g_strdup (_("An application is requesting additional packages."));
+       }
+
+       /* TRANSLATORS: notification title */
+       notification = g_notification_new (_("Additional Packages Required"));
+       g_notification_set_body (notification, body);
+       /* TRANSLATORS: this is a button that launches gnome-software */
+       g_notification_add_button_with_target (notification, _("Find in Software"), 
"app.install-provide-files", "(^asu)", files, 0);
+       g_notification_set_default_action_and_target (notification, "app.install-provide-files", "(^asu)", 
files, 0);
+       g_application_send_notification (g_application_get_default (), "install-provide-files", notification);
+}
+
+static gboolean
+handle_modify_install_provide_files (GsPackageKitModify                 *object,
+                                     GDBusMethodInvocation      *invocation,
+                                     guint                       arg_xid,
+                                     gchar                     **arg_files,
+                                     const gchar                *arg_interaction,
+                                     gpointer                    user_data)
+{
+       g_debug ("****** Modify.InstallProvideFiles");
+
+       notify_find_provide_files (NULL, arg_files);
+       gs_package_kit_modify_complete_install_provide_files (object, invocation);
+
+       return TRUE;
+}
+
+static void
+notify_find_package_names (const gchar  *desktop_id,
+                        gchar       **package_names)
+{
+       const gchar *app_name = NULL;
+       _cleanup_free_ gchar *body = NULL;
+       _cleanup_object_unref_ GDesktopAppInfo *app_info = NULL;
+       _cleanup_object_unref_ GNotification *notification = NULL;
+
+       if (desktop_id != NULL) {
+               app_info = g_desktop_app_info_new (desktop_id);
+               if (app_info != NULL)
+                       app_name = g_app_info_get_name (G_APP_INFO (app_info));
+       }
+
+       if (app_name != NULL) {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an app wants to install 
additional packages. */
+               body = g_strdup_printf (_("%s is requesting additional packages."), app_name);
+       } else {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an unknown wants to install 
additional packages.pes. */
+               body = g_strdup (_("An application is requesting additional packages."));
+       }
+
+       /* TRANSLATORS: notification title */
+       notification = g_notification_new (_("Additional Packages Required"));
+       g_notification_set_body (notification, body);
+       /* TRANSLATORS: this is a button that launches gnome-software */
+       g_notification_add_button_with_target (notification, _("Find in Software"), 
"app.install-package-names", "(^asu)", package_names, 0);
+       g_notification_set_default_action_and_target (notification, "app.install-package-names", "(^asu)", 
package_names, 0);
+       g_application_send_notification (g_application_get_default (), "install-package-names", notification);
+}
+
+static gboolean
+handle_modify_install_package_names (GsPackageKitModify                 *object,
+                                     GDBusMethodInvocation      *invocation,
+                                     guint                       arg_xid,
+                                     gchar                     **arg_package_names,
+                                     const gchar                *arg_interaction,
+                                     gpointer                    user_data)
+{
+       g_debug ("****** Modify.InstallPackageNames");
+
+       notify_find_package_names (NULL, arg_package_names);
+       gs_package_kit_modify_complete_install_package_names (object, invocation);
+
+       return TRUE;
+}
+
+static void
+notify_find_mime_types (const gchar     *desktop_id,
+                        gchar          **mime_types)
+{
+       const gchar *app_name = NULL;
+       _cleanup_free_ gchar *body = NULL;
+       _cleanup_object_unref_ GDesktopAppInfo *app_info = NULL;
+       _cleanup_object_unref_ GNotification *notification = NULL;
+
+       if (desktop_id != NULL) {
+               app_info = g_desktop_app_info_new (desktop_id);
+               if (app_info != NULL)
+                       app_name = g_app_info_get_name (G_APP_INFO (app_info));
+       }
+
+       if (app_name != NULL) {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an app needs additional 
MIME types. */
+               body = g_strdup_printf (_("%s is requesting additional MIME types."), app_name);
+       } else {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an unknown app needs 
additional MIME types. */
+               body = g_strdup (_("An application is requesting additional MIME types."));
+       }
+
+       /* TRANSLATORS: notification title */
+       notification = g_notification_new (_("Additional MIME Types Required"));
+       g_notification_set_body (notification, body);
+       /* TRANSLATORS: this is a button that launches gnome-software */
+       g_notification_add_button_with_target (notification, _("Find in Software"), "app.install-mime-types", 
"(^asu)", mime_types, 0);
+       g_notification_set_default_action_and_target (notification, "app.install-mime-types", "(^asu)", 
mime_types, 0);
+       g_application_send_notification (g_application_get_default (), "install-mime-types", notification);
+}
+
+static gboolean
+handle_modify_install_mime_types (GsPackageKitModify    *object,
+                                  GDBusMethodInvocation *invocation,
+                                  guint                  arg_xid,
+                                  gchar                **arg_mime_types,
+                                  const gchar           *arg_interaction,
+                                  gpointer               user_data)
+{
+       g_debug ("****** Modify.InstallMimeTypes");
+
+       notify_find_mime_types (NULL, arg_mime_types);
+       gs_package_kit_modify_complete_install_mime_types (object, invocation);
+
+       return TRUE;
+}
+
+static void
+notify_find_fontconfig_resources (const gchar   *desktop_id,
+                                  gchar                **resources)
+{
+       const gchar *app_name = NULL;
+       _cleanup_free_ gchar *body = NULL;
+       _cleanup_object_unref_ GDesktopAppInfo *app_info = NULL;
+       _cleanup_object_unref_ GNotification *notification = NULL;
+
+       if (desktop_id != NULL) {
+               app_info = g_desktop_app_info_new (desktop_id);
+               if (app_info != NULL)
+                       app_name = g_app_info_get_name (G_APP_INFO (app_info));
+       }
+
+       if (app_name != NULL) {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an app needs additional 
fonts. */
+               body = g_strdup_printf (_("%s is requesting additional fonts."), app_name);
+       } else {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an unknown app needs 
additional fonts. */
+               body = g_strdup (_("An application is requesting additional fonts."));
+       }
+
+       /* TRANSLATORS: notification title */
+       notification = g_notification_new (_("Additional Fonts Required"));
+       g_notification_set_body (notification, body);
+       /* TRANSLATORS: this is a button that launches gnome-software */
+       g_notification_add_button_with_target (notification, _("Find in Software"), 
"app.install-fontconfig-resources", "(^asu)", resources, 0);
+       g_notification_set_default_action_and_target (notification, "app.install-fontconfig-resources", 
"(^asu)", resources, 0);
+       g_application_send_notification (g_application_get_default (), "install-fontconfig-resources", 
notification);
+}
+
+static gboolean
+handle_modify_install_fontconfig_resources (GsPackageKitModify          *object,
+                                            GDBusMethodInvocation       *invocation,
+                                            guint                        arg_xid,
+                                            gchar                      **arg_resources,
+                                            const gchar                         *arg_interaction,
+                                            gpointer                     user_data)
+{
+       g_debug ("****** Modify.InstallFontconfigResources");
+
+       notify_find_fontconfig_resources (NULL, arg_resources);
+       gs_package_kit_modify_complete_install_fontconfig_resources (object, invocation);
+
+       return TRUE;
+}
+
+static void
+notify_find_gstreamer_resources (const gchar    *desktop_id,
+                                 gchar         **resources)
+{
+       const gchar *app_name = NULL;
+       _cleanup_free_ gchar *body = NULL;
+       _cleanup_object_unref_ GDesktopAppInfo *app_info = NULL;
+       _cleanup_object_unref_ GNotification *notification = NULL;
+
+       if (desktop_id != NULL) {
+               app_info = g_desktop_app_info_new (desktop_id);
+               if (app_info != NULL)
+                       app_name = g_app_info_get_name (G_APP_INFO (app_info));
+       }
+
+       if (app_name != NULL) {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an app needs additional 
codecs. */
+               body = g_strdup_printf (_("%s is requesting additional multimedia codecs."), app_name);
+       } else {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an unknown app needs 
additional codecs. */
+               body = g_strdup (_("An application is requesting additional multimedia codecs."));
+       }
+
+       /* TRANSLATORS: notification title */
+       notification = g_notification_new (_("Additional Multimedia Codecs Required"));
+       g_notification_set_body (notification, body);
+       /* TRANSLATORS: this is a button that launches gnome-software */
+       g_notification_add_button_with_target (notification, _("Find in Software"), 
"app.install-gstreamer-resources", "(^asu)", resources, 0);
+       g_notification_set_default_action_and_target (notification, "app.install-gstreamer-resources", 
"(^asu)", resources, 0);
+       g_application_send_notification (g_application_get_default (), "install-gstreamer-resources", 
notification);
+}
+
+static gboolean
+handle_modify_install_gstreamer_resources (GsPackageKitModify   *object,
+                                           GDBusMethodInvocation *invocation,
+                                           guint                 arg_xid,
+                                           gchar               **arg_resources,
+                                           const gchar          *arg_interaction,
+                                           gpointer              user_data)
+{
+       g_debug ("****** Modify.InstallGStreamerResources");
+
+       notify_find_gstreamer_resources (NULL, arg_resources);
+       gs_package_kit_modify_complete_install_gstreamer_resources (object, invocation);
+
+       return TRUE;
+}
+
+static void
+notify_find_plasma_resources (const gchar       *desktop_id,
+                              gchar            **resources)
+{
+       const gchar *app_name = NULL;
+       _cleanup_free_ gchar *body = NULL;
+       _cleanup_object_unref_ GDesktopAppInfo *app_info = NULL;
+       _cleanup_object_unref_ GNotification *notification = NULL;
+
+       if (desktop_id != NULL) {
+               app_info = g_desktop_app_info_new (desktop_id);
+               if (app_info != NULL)
+                       app_name = g_app_info_get_name (G_APP_INFO (app_info));
+       }
+
+       if (app_name != NULL) {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an app needs additional 
Plasma resources. */
+               body = g_strdup_printf (_("%s is requesting additional Plasma resources."), app_name);
+       } else {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an unknown app needs 
additional Plasma resources. */
+               body = g_strdup (_("An application is requesting additional Plasma resources."));
+       }
+
+       /* TRANSLATORS: notification title */
+       notification = g_notification_new (_("Additional Plasma Resources Required"));
+       g_notification_set_body (notification, body);
+       /* TRANSLATORS: this is a button that launches gnome-software */
+       g_notification_add_button_with_target (notification, _("Find in Software"), 
"app.install-plasma-resources", "(^asu)", resources, 0);
+       g_notification_set_default_action_and_target (notification, "app.install-plasma-resources", "(^asu)", 
resources, 0);
+       g_application_send_notification (g_application_get_default (), "install-plasma-resources", 
notification);
+}
+
+static gboolean
+handle_modify_install_resources (GsPackageKitModify     *object,
+                                 GDBusMethodInvocation  *invocation,
+                                 guint                   arg_xid,
+                                 const gchar            *arg_type,
+                                 gchar                 **arg_resources,
+                                 const gchar            *arg_interaction,
+                                 gpointer                user_data)
+{
+       gboolean ret;
+
+       g_debug ("****** Modify.InstallResources");
+
+       if (g_strcmp0 (arg_type, "plasma-service") == 0) {
+               notify_find_plasma_resources (NULL, arg_resources);
+               ret = TRUE;
+       } else {
+               ret = FALSE;
+       }
+       gs_package_kit_modify_complete_install_resources (object, invocation);
+
+       return ret;
+}
+
+static void
+notify_find_printer_drivers (const gchar        *desktop_id,
+                             gchar             **device_ids)
+{
+       const gchar *app_name = NULL;
+       _cleanup_free_ gchar *body = NULL;
+       _cleanup_object_unref_ GDesktopAppInfo *app_info = NULL;
+       _cleanup_object_unref_ GNotification *notification = NULL;
+
+       if (desktop_id != NULL) {
+               app_info = g_desktop_app_info_new (desktop_id);
+               if (app_info != NULL)
+                       app_name = g_app_info_get_name (G_APP_INFO (app_info));
+       }
+
+       if (app_name != NULL) {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an app needs additional 
printer drivers. */
+               body = g_strdup_printf (_("%s is requesting additional printer drivers."), app_name);
+       } else {
+               /* TRANSLATORS: this is a gnome-shell notification displayed when an unknown app needs 
additional printer drivers. */
+               body = g_strdup (_("An application is requesting additional printer drivers."));
+       }
+
+       /* TRANSLATORS: notification title */
+       notification = g_notification_new (_("Additional Printer Drivers Required"));
+       g_notification_set_body (notification, body);
+       /* TRANSLATORS: this is a button that launches gnome-software */
+       g_notification_add_button_with_target (notification, _("Find in Software"), 
"app.install-printer-drivers", "(^asu)", device_ids, 0);
+       g_notification_set_default_action_and_target (notification, "app.install-printer-drivers", "(^asu)", 
device_ids, 0);
+       g_application_send_notification (g_application_get_default (), "install-printer-drivers", 
notification);
+}
+
+static gboolean
+handle_modify_install_printer_drivers (GsPackageKitModify       *object,
+                                       GDBusMethodInvocation    *invocation,
+                                       guint                     arg_xid,
+                                       gchar                   **arg_device_ids,
+                                       const gchar              *arg_interaction,
+                                       gpointer                          user_data)
+{
+       g_debug ("****** Modify.InstallPrinterDrivers");
+
+       notify_find_printer_drivers (NULL, arg_device_ids);
+       gs_package_kit_modify_complete_install_printer_drivers (object, invocation);
+
+       return TRUE;
+}
+
+static void
 gs_dbus_helper_name_acquired_cb (GDBusConnection *connection,
                                 const gchar *name,
                                 gpointer user_data)
@@ -300,6 +695,7 @@ bus_gotten_cb (GObject      *source_object,
                return;
        }
 
+       /* Query interface */
        dbus_helper->query_interface = G_DBUS_INTERFACE_SKELETON (gs_package_kit_query_skeleton_new ());
 
        g_signal_connect (dbus_helper->query_interface, "handle-is-installed",
@@ -315,6 +711,34 @@ bus_gotten_cb (GObject      *source_object,
                return;
        }
 
+       /* Modify interface */
+       dbus_helper->modify_interface = G_DBUS_INTERFACE_SKELETON (gs_package_kit_modify_skeleton_new ());
+
+       g_signal_connect (dbus_helper->modify_interface, "handle-install-package-files",
+                         G_CALLBACK (handle_modify_install_package_files), dbus_helper);
+       g_signal_connect (dbus_helper->modify_interface, "handle-install-provide-files",
+                         G_CALLBACK (handle_modify_install_provide_files), dbus_helper);
+       g_signal_connect (dbus_helper->modify_interface, "handle-install-package-names",
+                         G_CALLBACK (handle_modify_install_package_names), dbus_helper);
+       g_signal_connect (dbus_helper->modify_interface, "handle-install-mime-types",
+                         G_CALLBACK (handle_modify_install_mime_types), dbus_helper);
+       g_signal_connect (dbus_helper->modify_interface, "handle-install-fontconfig-resources",
+                         G_CALLBACK (handle_modify_install_fontconfig_resources), dbus_helper);
+       g_signal_connect (dbus_helper->modify_interface, "handle-install-gstreamer-resources",
+                         G_CALLBACK (handle_modify_install_gstreamer_resources), dbus_helper);
+       g_signal_connect (dbus_helper->modify_interface, "handle-install-resources",
+                         G_CALLBACK (handle_modify_install_resources), dbus_helper);
+       g_signal_connect (dbus_helper->modify_interface, "handle-install-printer-drivers",
+                         G_CALLBACK (handle_modify_install_printer_drivers), dbus_helper);
+
+       if (!g_dbus_interface_skeleton_export (dbus_helper->modify_interface,
+                                              connection,
+                                              "/org/freedesktop/PackageKit",
+                                              &error)) {
+               g_warning ("Could not export dbus interface: %s", error->message);
+               return;
+       }
+
        dbus_helper->dbus_own_name_id = g_bus_own_name_on_connection (connection,
                                                                      "org.freedesktop.PackageKit2",
                                                                      G_BUS_NAME_OWNER_FLAGS_NONE,
@@ -355,6 +779,11 @@ gs_dbus_helper_dispose (GObject *object)
                g_clear_object (&dbus_helper->query_interface);
        }
 
+       if (dbus_helper->modify_interface != NULL) {
+               g_dbus_interface_skeleton_unexport (dbus_helper->modify_interface);
+               g_clear_object (&dbus_helper->modify_interface);
+       }
+
        g_clear_object (&dbus_helper->task);
 
        G_OBJECT_CLASS (gs_dbus_helper_parent_class)->dispose (object);
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index 0250698..9e1ac58 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -1735,6 +1735,340 @@ gs_plugin_loader_search_finish (GsPluginLoader *plugin_loader,
 /******************************************************************************/
 
 /**
+ * gs_plugin_loader_search_files_thread_cb:
+ **/
+static void
+gs_plugin_loader_search_files_thread_cb (GTask *task,
+                                         gpointer object,
+                                         gpointer task_data,
+                                         GCancellable *cancellable)
+{
+       const gchar *function_name = "gs_plugin_add_search_files";
+       gboolean ret = TRUE;
+       GError *error = NULL;
+       GsPluginLoaderAsyncState *state = (GsPluginLoaderAsyncState *) task_data;
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
+       GsPlugin *plugin;
+       GsPluginSearchFunc plugin_func = NULL;
+       guint i;
+       _cleanup_free_ gchar *profile_id = NULL;
+       _cleanup_strv_free_ gchar **values = NULL;
+
+       values = g_new0 (gchar *, 2);
+       values[0] = g_strdup (state->value);
+
+       /* run each plugin */
+       for (i = 0; i < plugin_loader->priv->plugins->len; i++) {
+               plugin = g_ptr_array_index (plugin_loader->priv->plugins, i);
+               if (!plugin->enabled)
+                       continue;
+               ret = g_task_return_error_if_cancelled (task);
+               if (ret)
+                       goto out;
+               ret = g_module_symbol (plugin->module,
+                                      function_name,
+                                      (gpointer *) &plugin_func);
+               if (!ret)
+                       continue;
+               profile_id = g_strdup_printf ("GsPlugin::%s(%s)",
+                                             plugin->name, function_name);
+               gs_profile_start (plugin_loader->priv->profile, profile_id);
+               ret = plugin_func (plugin, values, &state->list, cancellable, &error);
+               if (!ret) {
+                       g_task_return_error (task, error);
+                       goto out;
+               }
+               gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
+               gs_profile_stop (plugin_loader->priv->profile, profile_id);
+               g_clear_pointer (&profile_id, g_free);
+       }
+
+       /* dedupe applications we already know about */
+       gs_plugin_loader_list_dedupe (plugin_loader, state->list);
+
+       /* run refine() on each one */
+       ret = gs_plugin_loader_run_refine (plugin_loader,
+                                          function_name,
+                                          &state->list,
+                                          state->flags,
+                                          cancellable,
+                                          &error);
+       if (!ret) {
+               g_task_return_error (task, error);
+               goto out;
+       }
+
+       /* convert any unavailables */
+       gs_plugin_loader_convert_unavailable (state->list, state->value);
+
+       /* filter package list */
+       gs_plugin_list_filter_duplicates (&state->list);
+       gs_plugin_list_filter (&state->list, gs_plugin_loader_app_is_valid, NULL);
+       gs_plugin_list_filter (&state->list, gs_plugin_loader_app_is_non_installed, NULL);
+       gs_plugin_list_filter (&state->list, gs_plugin_loader_filter_qt_for_gtk, NULL);
+       gs_plugin_list_filter (&state->list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
+       if (g_settings_get_boolean (plugin_loader->priv->settings, "require-appdata")) {
+               gs_plugin_list_filter (&state->list,
+                                      gs_plugin_loader_get_app_has_appdata,
+                                      plugin_loader);
+       }
+       if (state->list == NULL) {
+               g_task_return_new_error (task,
+                                        GS_PLUGIN_LOADER_ERROR,
+                                        GS_PLUGIN_LOADER_ERROR_NO_RESULTS,
+                                        "no search results to show");
+               goto out;
+       }
+       if (g_list_length (state->list) > 500) {
+               g_task_return_new_error (task,
+                                        GS_PLUGIN_LOADER_ERROR,
+                                        GS_PLUGIN_LOADER_ERROR_NO_RESULTS,
+                                        "Too many search results returned");
+               goto out;
+       }
+
+       /* success */
+       g_task_return_pointer (task, gs_plugin_list_copy (state->list), (GDestroyNotify) gs_plugin_list_free);
+out:
+       if (profile_id != NULL)
+               gs_profile_stop (plugin_loader->priv->profile, profile_id);
+}
+
+/**
+ * gs_plugin_loader_search_files_async:
+ *
+ * This method calls all plugins that implement the gs_plugin_add_search_files()
+ * function. The plugins can either return #GsApp objects of kind
+ * %GS_APP_KIND_NORMAL for bonafide applications, or #GsApp's of kind
+ * %GS_APP_KIND_PACKAGE for packages that may or may not be applications.
+ *
+ * Once the list of updates is refined, some of the #GsApp's of kind
+ * %GS_APP_KIND_PACKAGE will have been promoted to a kind of %GS_APP_KIND_NORMAL,
+ * or if they are core applications, promoted again to a kind of %GS_APP_KIND_SYSTEM.
+ *
+ * Any #GsApp's of kind %GS_APP_KIND_PACKAGE or %GS_APP_KIND_SYSTEM that remain
+ * after refining are automatically removed.
+ *
+ * This means all of the #GsApp's returning from this function are of kind
+ * %GS_APP_KIND_NORMAL.
+ *
+ * The #GsApps may be in state %AS_APP_STATE_INSTALLED or %AS_APP_STATE_AVAILABLE
+ * and the UI may want to filter the two classes of applications differently.
+ **/
+void
+gs_plugin_loader_search_files_async (GsPluginLoader *plugin_loader,
+                                     const gchar *value,
+                                     GsPluginRefineFlags flags,
+                                     GCancellable *cancellable,
+                                     GAsyncReadyCallback callback,
+                                     gpointer user_data)
+{
+       GsPluginLoaderAsyncState *state;
+       _cleanup_object_unref_ GTask *task = NULL;
+
+       g_return_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader));
+       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+       /* save state */
+       state = g_slice_new0 (GsPluginLoaderAsyncState);
+       state->flags = flags;
+       state->value = g_strdup (value);
+
+       /* run in a thread */
+       task = g_task_new (plugin_loader, cancellable, callback, user_data);
+       g_task_set_task_data (task, state, (GDestroyNotify) gs_plugin_loader_free_async_state);
+       g_task_set_return_on_cancel (task, TRUE);
+       g_task_run_in_thread (task, gs_plugin_loader_search_files_thread_cb);
+}
+
+/**
+ * gs_plugin_loader_search_files_finish:
+ *
+ * Return value: (element-type GsApp) (transfer full): A list of applications
+ **/
+GList *
+gs_plugin_loader_search_files_finish (GsPluginLoader *plugin_loader,
+                                      GAsyncResult *res,
+                                      GError **error)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
+       g_return_val_if_fail (G_IS_TASK (res), NULL);
+       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
+       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+       return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+/******************************************************************************/
+
+/**
+ * gs_plugin_loader_search_what_provides_thread_cb:
+ **/
+static void
+gs_plugin_loader_search_what_provides_thread_cb (GTask *task,
+                                                 gpointer object,
+                                                 gpointer task_data,
+                                                 GCancellable *cancellable)
+{
+       const gchar *function_name = "gs_plugin_add_search_what_provides";
+       gboolean ret = TRUE;
+       GError *error = NULL;
+       GsPluginLoaderAsyncState *state = (GsPluginLoaderAsyncState *) task_data;
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
+       GsPlugin *plugin;
+       GsPluginSearchFunc plugin_func = NULL;
+       guint i;
+       _cleanup_free_ gchar *profile_id = NULL;
+       _cleanup_strv_free_ gchar **values = NULL;
+
+       values = g_new0 (gchar *, 2);
+       values[0] = g_strdup (state->value);
+
+       /* run each plugin */
+       for (i = 0; i < plugin_loader->priv->plugins->len; i++) {
+               plugin = g_ptr_array_index (plugin_loader->priv->plugins, i);
+               if (!plugin->enabled)
+                       continue;
+               ret = g_task_return_error_if_cancelled (task);
+               if (ret)
+                       goto out;
+               ret = g_module_symbol (plugin->module,
+                                      function_name,
+                                      (gpointer *) &plugin_func);
+               if (!ret)
+                       continue;
+               profile_id = g_strdup_printf ("GsPlugin::%s(%s)",
+                                             plugin->name, function_name);
+               gs_profile_start (plugin_loader->priv->profile, profile_id);
+               ret = plugin_func (plugin, values, &state->list, cancellable, &error);
+               if (!ret) {
+                       g_task_return_error (task, error);
+                       goto out;
+               }
+               gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
+               gs_profile_stop (plugin_loader->priv->profile, profile_id);
+               g_clear_pointer (&profile_id, g_free);
+       }
+
+       /* dedupe applications we already know about */
+       gs_plugin_loader_list_dedupe (plugin_loader, state->list);
+
+       /* run refine() on each one */
+       ret = gs_plugin_loader_run_refine (plugin_loader,
+                                          function_name,
+                                          &state->list,
+                                          state->flags,
+                                          cancellable,
+                                          &error);
+       if (!ret) {
+               g_task_return_error (task, error);
+               goto out;
+       }
+
+       /* convert any unavailables */
+       gs_plugin_loader_convert_unavailable (state->list, state->value);
+
+       /* filter package list */
+       gs_plugin_list_filter_duplicates (&state->list);
+       gs_plugin_list_filter (&state->list, gs_plugin_loader_app_is_valid, NULL);
+       gs_plugin_list_filter (&state->list, gs_plugin_loader_app_is_non_installed, NULL);
+       gs_plugin_list_filter (&state->list, gs_plugin_loader_filter_qt_for_gtk, NULL);
+       gs_plugin_list_filter (&state->list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
+       if (g_settings_get_boolean (plugin_loader->priv->settings, "require-appdata")) {
+               gs_plugin_list_filter (&state->list,
+                                      gs_plugin_loader_get_app_has_appdata,
+                                      plugin_loader);
+       }
+       if (state->list == NULL) {
+               g_task_return_new_error (task,
+                                        GS_PLUGIN_LOADER_ERROR,
+                                        GS_PLUGIN_LOADER_ERROR_NO_RESULTS,
+                                        "no search results to show");
+               goto out;
+       }
+       if (g_list_length (state->list) > 500) {
+               g_task_return_new_error (task,
+                                        GS_PLUGIN_LOADER_ERROR,
+                                        GS_PLUGIN_LOADER_ERROR_NO_RESULTS,
+                                        "Too many search results returned");
+               goto out;
+       }
+
+       /* success */
+       g_task_return_pointer (task, gs_plugin_list_copy (state->list), (GDestroyNotify) gs_plugin_list_free);
+out:
+       if (profile_id != NULL)
+               gs_profile_stop (plugin_loader->priv->profile, profile_id);
+}
+
+/**
+ * gs_plugin_loader_search_what_provides_async:
+ *
+ * This method calls all plugins that implement the gs_plugin_add_search_what_provides()
+ * function. The plugins can either return #GsApp objects of kind
+ * %GS_APP_KIND_NORMAL for bonafide applications, or #GsApp's of kind
+ * %GS_APP_KIND_PACKAGE for packages that may or may not be applications.
+ *
+ * Once the list of updates is refined, some of the #GsApp's of kind
+ * %GS_APP_KIND_PACKAGE will have been promoted to a kind of %GS_APP_KIND_NORMAL,
+ * or if they are core applications, promoted again to a kind of %GS_APP_KIND_SYSTEM.
+ *
+ * Any #GsApp's of kind %GS_APP_KIND_PACKAGE or %GS_APP_KIND_SYSTEM that remain
+ * after refining are automatically removed.
+ *
+ * This means all of the #GsApp's returning from this function are of kind
+ * %GS_APP_KIND_NORMAL.
+ *
+ * The #GsApps may be in state %AS_APP_STATE_INSTALLED or %AS_APP_STATE_AVAILABLE
+ * and the UI may want to filter the two classes of applications differently.
+ **/
+void
+gs_plugin_loader_search_what_provides_async (GsPluginLoader *plugin_loader,
+                                             const gchar *value,
+                                             GsPluginRefineFlags flags,
+                                             GCancellable *cancellable,
+                                             GAsyncReadyCallback callback,
+                                             gpointer user_data)
+{
+       GsPluginLoaderAsyncState *state;
+       _cleanup_object_unref_ GTask *task = NULL;
+
+       g_return_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader));
+       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+       /* save state */
+       state = g_slice_new0 (GsPluginLoaderAsyncState);
+       state->flags = flags;
+       state->value = g_strdup (value);
+
+       /* run in a thread */
+       task = g_task_new (plugin_loader, cancellable, callback, user_data);
+       g_task_set_task_data (task, state, (GDestroyNotify) gs_plugin_loader_free_async_state);
+       g_task_set_return_on_cancel (task, TRUE);
+       g_task_run_in_thread (task, gs_plugin_loader_search_what_provides_thread_cb);
+}
+
+/**
+ * gs_plugin_loader_search_what_provides_finish:
+ *
+ * Return value: (element-type GsApp) (transfer full): A list of applications
+ **/
+GList *
+gs_plugin_loader_search_what_provides_finish (GsPluginLoader *plugin_loader,
+                                              GAsyncResult *res,
+                                              GError **error)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
+       g_return_val_if_fail (G_IS_TASK (res), NULL);
+       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
+       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+       return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+/******************************************************************************/
+
+/**
  * gs_plugin_loader_category_sort_cb:
  **/
 static gint
diff --git a/src/gs-plugin-loader.h b/src/gs-plugin-loader.h
index 474de8a..1146e81 100644
--- a/src/gs-plugin-loader.h
+++ b/src/gs-plugin-loader.h
@@ -146,6 +146,24 @@ void                gs_plugin_loader_search_async          (GsPluginLoader 
*plugin_loader,
 GList          *gs_plugin_loader_search_finish         (GsPluginLoader *plugin_loader,
                                                         GAsyncResult   *res,
                                                         GError         **error);
+void            gs_plugin_loader_search_files_async    (GsPluginLoader *plugin_loader,
+                                                        const gchar    *value,
+                                                        GsPluginRefineFlags flags,
+                                                        GCancellable   *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer        user_data);
+GList          *gs_plugin_loader_search_files_finish   (GsPluginLoader *plugin_loader,
+                                                        GAsyncResult   *res,
+                                                        GError         **error);
+void            gs_plugin_loader_search_what_provides_async (GsPluginLoader    *plugin_loader,
+                                                        const gchar    *value,
+                                                        GsPluginRefineFlags flags,
+                                                        GCancellable   *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer        user_data);
+GList          *gs_plugin_loader_search_what_provides_finish (GsPluginLoader   *plugin_loader,
+                                                        GAsyncResult   *res,
+                                                        GError         **error);
 void            gs_plugin_loader_filename_to_app_async (GsPluginLoader *plugin_loader,
                                                         const gchar    *filename,
                                                         GsPluginRefineFlags flags,
diff --git a/src/gs-plugin.h b/src/gs-plugin.h
index 5c9399e..94c5268 100644
--- a/src/gs-plugin.h
+++ b/src/gs-plugin.h
@@ -180,6 +180,16 @@ gboolean    gs_plugin_add_search                   (GsPlugin       *plugin,
                                                         GList          **list,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+gboolean        gs_plugin_add_search_files             (GsPlugin       *plugin,
+                                                        gchar          **values,
+                                                        GList          **list,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
+gboolean        gs_plugin_add_search_what_provides     (GsPlugin       *plugin,
+                                                        gchar          **values,
+                                                        GList          **list,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 const gchar    **gs_plugin_get_deps                    (GsPlugin       *plugin);
 gboolean        gs_plugin_add_installed                (GsPlugin       *plugin,
                                                         GList          **list,
diff --git a/src/gs-shell-extras.c b/src/gs-shell-extras.c
new file mode 100644
index 0000000..807750c
--- /dev/null
+++ b/src/gs-shell-extras.c
@@ -0,0 +1,1124 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013-2014 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gs-cleanup.h"
+#include "gs-shell.h"
+#include "gs-shell-extras.h"
+#include "gs-utils.h"
+#include "gs-app.h"
+#include "gs-app-row.h"
+#include "gs-markdown.h"
+
+typedef enum {
+       GS_SHELL_EXTRAS_STATE_LOADING,
+       GS_SHELL_EXTRAS_STATE_READY,
+       GS_SHELL_EXTRAS_STATE_NO_RESULTS,
+       GS_SHELL_EXTRAS_STATE_FAILED
+} GsShellExtrasState;
+
+typedef struct {
+       gchar           *title;
+       gchar           *search;
+       gchar           *search_filename;
+       gchar           *package_filename;
+       gchar           *url_not_found;
+       gchar           *summary_missing;
+       GsShellExtras   *shell_extras;
+} SearchData;
+
+struct GsShellExtrasPrivate
+{
+       GsPluginLoader           *plugin_loader;
+       GtkBuilder               *builder;
+       GCancellable             *cancellable;
+       GCancellable             *search_cancellable;
+       GsShell                  *shell;
+       GsShellExtrasState        state;
+       GtkSizeGroup             *sizegroup_image;
+       GtkSizeGroup             *sizegroup_name;
+       GPtrArray                *array_search_data;
+       guint                     pending_search_cnt;
+
+       GtkWidget                *label_failed;
+       GtkWidget                *label_no_results;
+       GtkWidget                *list_box_results;
+       GtkWidget                *scrolledwindow;
+       GtkWidget                *spinner;
+       GtkWidget                *stack;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GsShellExtras, gs_shell_extras, GS_TYPE_PAGE)
+
+static void
+search_data_free (SearchData *search_data)
+{
+       if (search_data->shell_extras != NULL)
+               g_object_unref (search_data->shell_extras);
+       g_free (search_data->title);
+       g_free (search_data->search);
+       g_free (search_data->search_filename);
+       g_free (search_data->package_filename);
+       g_free (search_data->url_not_found);
+       g_slice_free (SearchData, search_data);
+}
+
+static gchar *
+build_comma_separated_list (gchar **items)
+{
+       gchar *list;
+       guint len;
+
+       len = g_strv_length (items);
+       if (len == 2) {
+               /* TRANSLATORS: separator for a list of items */
+               list = g_strjoinv (_(" and "), items);
+       } else {
+               /* TRANSLATORS: separator for a list of items */
+               list = g_strjoinv (_(", "), items);
+       }
+
+       return list;
+}
+
+static gchar *
+build_title (GsShellExtras *shell_extras)
+{
+       GsShellExtrasPrivate *priv = shell_extras->priv;
+       guint i;
+       _cleanup_free_ gchar *titles = NULL;
+       _cleanup_ptrarray_unref_ GPtrArray *title_array = NULL;
+
+       title_array = g_ptr_array_new ();
+       for (i = 0; i < priv->array_search_data->len; i++) {
+               SearchData *search_data;
+
+               search_data = g_ptr_array_index (priv->array_search_data, i);
+               g_ptr_array_add (title_array, search_data->title);
+       }
+       g_ptr_array_add (title_array, NULL);
+
+       titles = build_comma_separated_list ((gchar **) title_array->pdata);
+       /* TRANSLATORS: Application window title for codec installation. %s will be replaced by actual codec 
name(s) */
+       return g_strdup_printf (ngettext ("Available software for %s",
+                                         "Available software for %s",
+                                         priv->array_search_data->len),
+                               titles);
+}
+
+static void
+gs_shell_extras_update_ui_state (GsShellExtras *shell_extras)
+{
+       GsShellExtrasPrivate *priv = shell_extras->priv;
+       GtkWidget *widget;
+       _cleanup_free_ gchar *title = NULL;
+
+       if (gs_shell_get_mode (shell_extras->priv->shell) != GS_SHELL_MODE_EXTRAS)
+               return;
+
+       /* main spinner */
+       switch (priv->state) {
+       case GS_SHELL_EXTRAS_STATE_LOADING:
+               gs_start_spinner (GTK_SPINNER (priv->spinner));
+               break;
+       case GS_SHELL_EXTRAS_STATE_READY:
+       case GS_SHELL_EXTRAS_STATE_NO_RESULTS:
+       case GS_SHELL_EXTRAS_STATE_FAILED:
+               gs_stop_spinner (GTK_SPINNER (priv->spinner));
+               break;
+       default:
+               g_assert_not_reached ();
+               break;
+       }
+
+       /* headerbar title */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "application_details_header"));
+       switch (priv->state) {
+       case GS_SHELL_EXTRAS_STATE_LOADING:
+       case GS_SHELL_EXTRAS_STATE_READY:
+               title = build_title (shell_extras);
+               gtk_label_set_label (GTK_LABEL (widget), title);
+               break;
+       case GS_SHELL_EXTRAS_STATE_NO_RESULTS:
+       case GS_SHELL_EXTRAS_STATE_FAILED:
+               gtk_label_set_label (GTK_LABEL (widget), _("Unable to Find Requested Software"));
+               break;
+       default:
+               g_assert_not_reached ();
+               break;
+       }
+
+       /* stack */
+       switch (priv->state) {
+       case GS_SHELL_EXTRAS_STATE_LOADING:
+               gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "spinner");
+               break;
+       case GS_SHELL_EXTRAS_STATE_READY:
+               gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "results");
+               break;
+       case GS_SHELL_EXTRAS_STATE_NO_RESULTS:
+               gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "no-results");
+               break;
+       case GS_SHELL_EXTRAS_STATE_FAILED:
+               gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "failed");
+               break;
+       default:
+               g_assert_not_reached ();
+               break;
+       }
+}
+
+static void
+gs_shell_extras_set_state (GsShellExtras *shell_extras,
+                           GsShellExtrasState state)
+{
+       shell_extras->priv->state = state;
+       gs_shell_extras_update_ui_state (shell_extras);
+}
+
+static void
+app_row_button_clicked_cb (GsAppRow *app_row,
+                           GsShellExtras *shell_extras)
+{
+       GsApp *app;
+       app = gs_app_row_get_app (app_row);
+       if (gs_app_get_state (app) == AS_APP_STATE_AVAILABLE ||
+           gs_app_get_state (app) == AS_APP_STATE_AVAILABLE_LOCAL)
+               gs_page_install_app (GS_PAGE (shell_extras), app);
+       else if (gs_app_get_state (app) == AS_APP_STATE_INSTALLED)
+               gs_page_remove_app (GS_PAGE (shell_extras), app);
+       else
+               g_critical ("codecs: app in unexpected state %d", gs_app_get_state (app));
+}
+
+static void
+gs_shell_extras_add_app (GsShellExtras *shell_extras, GsApp *app, SearchData *search_data)
+{
+       GsShellExtrasPrivate *priv = shell_extras->priv;
+       GtkWidget *app_row;
+       GList *l;
+       _cleanup_list_free_ GList *list = NULL;
+
+       /* Don't add same app twice */
+       list = gtk_container_get_children (GTK_CONTAINER (priv->list_box_results));
+       for (l = list; l != NULL; l = l->next) {
+               GsApp *existing_app;
+
+               existing_app = gs_app_row_get_app (GS_APP_ROW (l->data));
+               if (app == existing_app)
+                       gtk_container_remove (GTK_CONTAINER (priv->list_box_results),
+                                             GTK_WIDGET (l->data));
+       }
+
+       app_row = gs_app_row_new ();
+       gs_app_row_set_show_codec (GS_APP_ROW (app_row), TRUE);
+       gs_app_row_set_app (GS_APP_ROW (app_row), app);
+
+       g_object_set_data_full (G_OBJECT (app_row), "missing-title", g_strdup (search_data->title), g_free);
+
+       g_signal_connect (app_row, "button-clicked",
+                         G_CALLBACK (app_row_button_clicked_cb),
+                         shell_extras);
+
+       gtk_container_add (GTK_CONTAINER (priv->list_box_results), app_row);
+       gs_app_row_set_size_groups (GS_APP_ROW (app_row),
+                                   priv->sizegroup_image,
+                                   priv->sizegroup_name);
+       gtk_widget_show (app_row);
+}
+
+static GsApp *
+create_missing_codec_app (SearchData *search_data)
+{
+       GsApp *app;
+       _cleanup_object_unref_ AsIcon *icon = NULL;
+       _cleanup_free_ gchar *name = NULL;
+       _cleanup_free_ gchar *description = NULL;
+
+       app = gs_app_new ("missing-codec");
+
+       /* TRANSLATORS: This string is used for codecs that weren't found */
+       name = g_strdup_printf (_("%s not found"), search_data->title);
+       gs_app_set_name (app, GS_APP_QUALITY_HIGHEST, name);
+       gs_app_set_summary_missing (app, search_data->summary_missing);
+
+       gs_app_set_kind (app, GS_APP_KIND_MISSING);
+       gs_app_set_size (app, GS_APP_SIZE_MISSING);
+       gs_app_set_url (app, AS_URL_KIND_MISSING, search_data->url_not_found);
+
+       icon = as_icon_new ();
+       as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
+       as_icon_set_name (icon, "dialog-question-symbolic", -1);
+       gs_app_set_icon (app, icon);
+       gs_app_load_icon (app, 1, NULL);
+
+       return app;
+}
+
+static gchar *
+build_no_results_label (GsShellExtras *shell_extras)
+{
+       GsShellExtrasPrivate *priv = shell_extras->priv;
+       GList *l;
+       GsApp *app;
+       guint num;
+       _cleanup_free_ gchar *codec_titles = NULL;
+       _cleanup_free_ gchar *url = NULL;
+       _cleanup_list_free_ GList *list = NULL;
+       _cleanup_ptrarray_unref_ GPtrArray *array = NULL;
+
+       list = gtk_container_get_children (GTK_CONTAINER (priv->list_box_results));
+       num = g_list_length (list);
+
+       g_assert (num > 0);
+
+       array = g_ptr_array_new ();
+       for (l = list; l != NULL; l = l->next) {
+               app = gs_app_row_get_app (GS_APP_ROW (l->data));
+               g_ptr_array_add (array,
+                                g_object_get_data (G_OBJECT (l->data), "missing-title"));
+       }
+       g_ptr_array_add (array, NULL);
+
+       /* TRANSLATORS: hyperlink title */
+       url = g_strdup_printf ("<a href=\"%s\">%s</a>",
+                              gs_app_get_url (app, AS_URL_KIND_MISSING),
+                               _("this website"));
+
+       codec_titles = build_comma_separated_list ((gchar **) array->pdata);
+       /* TRANSLATORS: no codecs were found. First %s will be replaced by actual codec name(s), second %s is 
a link titled "this website" */
+       return g_strdup_printf (ngettext ("Unfortunately, the %s you were searching for could not be found. 
Please see %s for more information.",
+                                         "Unfortunately, the %s you were searching for could not be found. 
Please see %s for more information.",
+                                         num),
+                               codec_titles,
+                               url);
+}
+
+static void
+show_search_results (GsShellExtras *shell_extras)
+{
+       GsShellExtrasPrivate *priv = shell_extras->priv;
+       GsApp *app;
+       GList *l;
+       guint n_children;
+       guint n_missing;
+       _cleanup_list_free_ GList *list = NULL;
+
+       list = gtk_container_get_children (GTK_CONTAINER (priv->list_box_results));
+       n_children = g_list_length (list);
+
+       /* count the number of rows with missing codecs */
+       n_missing = 0;
+       for (l = list; l != NULL; l = l->next) {
+               app = gs_app_row_get_app (GS_APP_ROW (l->data));
+               if (g_strcmp0 (gs_app_get_id (app), "missing-codec") == 0) {
+                       n_missing++;
+               }
+       }
+
+       if (n_children == 0 || n_children == n_missing) {
+               _cleanup_free_ gchar *str = NULL;
+
+               /* no results */
+               g_debug ("codecs: failed to find any results, %d", n_missing);
+               str = build_no_results_label (shell_extras);
+               gtk_label_set_label (GTK_LABEL (priv->label_no_results), str);
+               gs_shell_extras_set_state (shell_extras,
+                                          GS_SHELL_EXTRAS_STATE_NO_RESULTS);
+       } else if (n_children == 1) {
+               /* switch directly to details view */
+               g_debug ("codecs: found one result, showing in details view");
+               app = gs_app_row_get_app (GS_APP_ROW (list->data));
+               gs_shell_change_mode (priv->shell, GS_SHELL_MODE_DETAILS, app, NULL, TRUE);
+       } else {
+               /* show what we got */
+               g_debug ("codecs: got %d search results, showing", n_children);
+               gs_shell_extras_set_state (shell_extras,
+                                          GS_SHELL_EXTRAS_STATE_READY);
+       }
+}
+
+static void
+search_files_cb (GObject *source_object,
+                 GAsyncResult *res,
+                 gpointer user_data)
+{
+       SearchData *search_data = (SearchData *) user_data;
+       GsShellExtras *shell_extras = search_data->shell_extras;
+       GsShellExtrasPrivate *priv = shell_extras->priv;
+       _cleanup_plugin_list_free_ GList *list = NULL;
+       GList *l;
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
+       _cleanup_error_free_ GError *error = NULL;
+
+       list = gs_plugin_loader_search_what_provides_finish (plugin_loader, res, &error);
+       if (list == NULL) {
+               if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                       g_debug ("codecs: search what provides cancelled");
+                       return;
+               }
+               if (g_error_matches (error,
+                                    GS_PLUGIN_LOADER_ERROR,
+                                    GS_PLUGIN_LOADER_ERROR_NO_RESULTS)) {
+                       GsApp *app;
+
+                       g_debug ("codecs: no search result for %s, showing as missing", search_data->title);
+                       app = create_missing_codec_app (search_data);
+                       list = g_list_prepend (list, app);
+               } else {
+                       _cleanup_free_ gchar *str = NULL;
+
+                       g_warning ("failed to find any search results: %s", error->message);
+                       str = g_strdup_printf (_("Failed to find any search results: %s"), error->message);
+                       gtk_label_set_label (GTK_LABEL (priv->label_failed), str);
+                       gs_shell_extras_set_state (shell_extras,
+                                                   GS_SHELL_EXTRAS_STATE_FAILED);
+                       return;
+               }
+       }
+
+       for (l = list; l != NULL; l = l->next) {
+               GsApp *app = GS_APP (l->data);
+
+               g_debug ("%s\n\n", gs_app_to_string (app));
+               gs_shell_extras_add_app (shell_extras, app, search_data);
+       }
+
+       priv->pending_search_cnt--;
+
+       /* have all searches finished? */
+       if (priv->pending_search_cnt == 0)
+               show_search_results (shell_extras);
+}
+
+static void
+filename_to_app_cb (GObject *source_object,
+                    GAsyncResult *res,
+                    gpointer user_data)
+{
+       SearchData *search_data = (SearchData *) user_data;
+       GsShellExtras *shell_extras = search_data->shell_extras;
+       GsShellExtrasPrivate *priv = shell_extras->priv;
+       GsApp *app;
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
+       _cleanup_error_free_ GError *error = NULL;
+
+       app = gs_plugin_loader_filename_to_app_finish (plugin_loader, res, &error);
+       if (app == NULL) {
+               if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                       g_debug ("codecs: search what provides cancelled");
+                       return;
+               }
+               if (g_error_matches (error,
+                                    GS_PLUGIN_LOADER_ERROR,
+                                    GS_PLUGIN_LOADER_ERROR_NO_RESULTS)) {
+                       g_debug ("codecs: no search result for %s, showing as missing", search_data->title);
+                       app = create_missing_codec_app (search_data);
+               } else {
+                       _cleanup_free_ gchar *str = NULL;
+
+                       g_warning ("failed to find any search results: %s", error->message);
+                       str = g_strdup_printf (_("Failed to find any search results: %s"), error->message);
+                       gtk_label_set_label (GTK_LABEL (priv->label_failed), str);
+                       gs_shell_extras_set_state (shell_extras,
+                                                   GS_SHELL_EXTRAS_STATE_FAILED);
+                       return;
+               }
+       }
+
+       g_debug ("%s\n\n", gs_app_to_string (app));
+       gs_shell_extras_add_app (shell_extras, app, search_data);
+
+       priv->pending_search_cnt--;
+
+       /* have all searches finished? */
+       if (priv->pending_search_cnt == 0)
+               show_search_results (shell_extras);
+
+       g_object_unref (app);
+}
+
+static void
+get_search_what_provides_cb (GObject *source_object,
+                             GAsyncResult *res,
+                             gpointer user_data)
+{
+       SearchData *search_data = (SearchData *) user_data;
+       GsShellExtras *shell_extras = search_data->shell_extras;
+       GsShellExtrasPrivate *priv = shell_extras->priv;
+       _cleanup_plugin_list_free_ GList *list = NULL;
+       GList *l;
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
+       _cleanup_error_free_ GError *error = NULL;
+
+       list = gs_plugin_loader_search_what_provides_finish (plugin_loader, res, &error);
+       if (list == NULL) {
+               if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                       g_debug ("codecs: search what provides cancelled");
+                       return;
+               }
+               if (g_error_matches (error,
+                                    GS_PLUGIN_LOADER_ERROR,
+                                    GS_PLUGIN_LOADER_ERROR_NO_RESULTS)) {
+                       GsApp *app;
+
+                       g_debug ("codecs: no search result for %s, showing as missing", search_data->title);
+                       app = create_missing_codec_app (search_data);
+                       list = g_list_prepend (list, app);
+               } else {
+                       _cleanup_free_ gchar *str = NULL;
+
+                       g_warning ("failed to find any search results: %s", error->message);
+                       str = g_strdup_printf (_("Failed to find any search results: %s"), error->message);
+                       gtk_label_set_label (GTK_LABEL (priv->label_failed), str);
+                       gs_shell_extras_set_state (shell_extras,
+                                                   GS_SHELL_EXTRAS_STATE_FAILED);
+                       return;
+               }
+       }
+
+       for (l = list; l != NULL; l = l->next) {
+               GsApp *app = GS_APP (l->data);
+
+               g_debug ("%s\n\n", gs_app_to_string (app));
+               gs_shell_extras_add_app (shell_extras, app, search_data);
+       }
+
+       priv->pending_search_cnt--;
+
+       /* have all searches finished? */
+       if (priv->pending_search_cnt == 0)
+               show_search_results (shell_extras);
+}
+
+static void
+gs_shell_extras_load (GsShellExtras *shell_extras, GPtrArray *array_search_data)
+{
+       GsShellExtrasPrivate *priv = shell_extras->priv;
+       guint i;
+
+       /* cancel any pending searches */
+       if (priv->search_cancellable != NULL) {
+               g_cancellable_cancel (priv->search_cancellable);
+               g_object_unref (priv->search_cancellable);
+       }
+       priv->search_cancellable = g_cancellable_new ();
+
+       if (array_search_data != NULL) {
+               if (priv->array_search_data != NULL)
+                       g_ptr_array_unref (priv->array_search_data);
+               priv->array_search_data = g_ptr_array_ref (array_search_data);
+       }
+
+       priv->pending_search_cnt = 0;
+
+       /* remove old entries */
+       gs_container_remove_all (GTK_CONTAINER (priv->list_box_results));
+
+       /* start new searches, separate one for each codec */
+       for (i = 0; i < priv->array_search_data->len; i++) {
+               SearchData *search_data;
+
+               search_data = g_ptr_array_index (priv->array_search_data, i);
+               if (search_data->search_filename != NULL) {
+                       g_debug ("searching filename: '%s'\n", search_data->search_filename);
+                       gs_plugin_loader_search_files_async (priv->plugin_loader,
+                                                            search_data->search_filename,
+                                                            GS_PLUGIN_REFINE_FLAGS_DEFAULT |
+                                                            GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING,
+                                                            priv->search_cancellable,
+                                                            search_files_cb,
+                                                            search_data);
+               } else if (search_data->package_filename != NULL) {
+                       g_debug ("resolving filename to app: '%s'\n", search_data->package_filename);
+                       gs_plugin_loader_filename_to_app_async (priv->plugin_loader,
+                                                               search_data->package_filename,
+                                                               GS_PLUGIN_REFINE_FLAGS_DEFAULT |
+                                                               GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING,
+                                                               priv->search_cancellable,
+                                                               filename_to_app_cb,
+                                                               search_data);
+               } else {
+                       g_debug ("searching what provides: '%s'\n", search_data->search);
+                       gs_plugin_loader_search_what_provides_async (priv->plugin_loader,
+                                                                    search_data->search,
+                                                                    GS_PLUGIN_REFINE_FLAGS_DEFAULT |
+                                                                    GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                                                    GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY |
+                                                                    
GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
+                                                                    
GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION |
+                                                                    GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING,
+                                                                    priv->search_cancellable,
+                                                                    get_search_what_provides_cb,
+                                                                    search_data);
+               }
+               priv->pending_search_cnt++;
+       }
+}
+
+void
+gs_shell_extras_reload (GsShellExtras *shell_extras)
+{
+       gs_shell_extras_load (shell_extras, NULL);
+}
+
+void gs_shell_extras_search_package_files (GsShellExtras *shell_extras, gchar **files)
+{
+       _cleanup_ptrarray_unref_ GPtrArray *array_search_data = g_ptr_array_new_with_free_func 
((GDestroyNotify) search_data_free);
+       guint i;
+
+       for (i = 0; files[i] != NULL; i++) {
+               GString *tmp;
+               SearchData *search_data;
+               _cleanup_free_ gchar *url = NULL;
+
+               search_data = g_slice_new0 (SearchData);
+               search_data->title = g_strdup (files[i]);
+               search_data->package_filename = g_strdup (files[i]);
+               search_data->url_not_found = g_strdup 
("https://fedoraproject.org/wiki/PackageKit_Items_Not_Found#Missing_Package";);
+
+               tmp = g_string_new ("");
+               /* TRANSLATORS: this is when we know about an application or
+                * addon, but it can't be listed for some reason */
+               g_string_append_printf (tmp, _("No applications are available that provide the file %s."), 
search_data->title);
+               g_string_append (tmp, "\n");
+               /* TRANSLATORS: hyperlink title */
+               url = g_strdup_printf ("<a href=\"%s\">%s</a>",
+                                      search_data->url_not_found,
+                                      _("on the website"));
+               /* TRANSLATORS: first %s is the codec name, and second %s is a
+                 * hyperlink with the "on the website" text */
+               g_string_append_printf (tmp, _("Information about %s, as well as options "
+                                       "for how to get missing applications "
+                                       "can be found %s."), search_data->title, url);
+
+               search_data->summary_missing = g_string_free (tmp, FALSE);
+               search_data->shell_extras = g_object_ref (shell_extras);
+               g_ptr_array_add (array_search_data, search_data);
+       }
+
+       gs_shell_extras_load (shell_extras, array_search_data);
+}
+
+void gs_shell_extras_search_provide_files (GsShellExtras *shell_extras, gchar **files)
+{
+       _cleanup_ptrarray_unref_ GPtrArray *array_search_data = g_ptr_array_new_with_free_func 
((GDestroyNotify) search_data_free);
+       guint i;
+
+       for (i = 0; files[i] != NULL; i++) {
+               GString *tmp;
+               SearchData *search_data;
+               _cleanup_free_ gchar *url = NULL;
+
+               search_data = g_slice_new0 (SearchData);
+               search_data->title = g_strdup (files[i]);
+               search_data->search_filename = g_strdup (files[i]);
+               search_data->url_not_found = g_strdup 
("https://fedoraproject.org/wiki/PackageKit_Items_Not_Found#Missing_Package";);
+
+               tmp = g_string_new ("");
+               /* TRANSLATORS: this is when we know about an application or
+                * addon, but it can't be listed for some reason */
+               g_string_append_printf (tmp, _("No applications are available for %s support."), 
search_data->title);
+               g_string_append (tmp, "\n");
+               /* TRANSLATORS: hyperlink title */
+               url = g_strdup_printf ("<a href=\"%s\">%s</a>",
+                                      search_data->url_not_found,
+                                      _("on the website"));
+               /* TRANSLATORS: first %s is the codec name, and second %s is a
+                 * hyperlink with the "on the website" text */
+               g_string_append_printf (tmp, _("Information about %s, as well as options "
+                                       "for how to get missing applications "
+                                       "can be found %s."), search_data->title, url);
+
+               search_data->summary_missing = g_string_free (tmp, FALSE);
+               search_data->shell_extras = g_object_ref (shell_extras);
+               g_ptr_array_add (array_search_data, search_data);
+       }
+
+       gs_shell_extras_load (shell_extras, array_search_data);
+}
+
+void gs_shell_extras_search_package_names (GsShellExtras *shell_extras, gchar **package_names)
+{
+       _cleanup_ptrarray_unref_ GPtrArray *array_search_data = g_ptr_array_new_with_free_func 
((GDestroyNotify) search_data_free);
+       guint i;
+
+       for (i = 0; package_names[i] != NULL; i++) {
+               GString *tmp;
+               SearchData *search_data;
+               _cleanup_free_ gchar *url = NULL;
+
+               search_data = g_slice_new0 (SearchData);
+               search_data->title = g_strdup (package_names[i]);
+               search_data->search = g_strdup (package_names[i]);
+               search_data->url_not_found = g_strdup 
("https://fedoraproject.org/wiki/PackageKit_Items_Not_Found#Missing_Package";);
+
+               tmp = g_string_new ("");
+               /* TRANSLATORS: this is when we know about an application or
+                * addon, but it can't be listed for some reason */
+               g_string_append_printf (tmp, _("No applications are available for %s support."), 
search_data->title);
+               g_string_append (tmp, "\n");
+               /* TRANSLATORS: hyperlink title */
+               url = g_strdup_printf ("<a href=\"%s\">%s</a>",
+                                      search_data->url_not_found,
+                                      _("on the website"));
+               /* TRANSLATORS: first %s is the codec name, and second %s is a
+                 * hyperlink with the "on the website" text */
+               g_string_append_printf (tmp, _("Information about %s, as well as options "
+                                       "for how to get missing applications "
+                                       "can be found %s."), search_data->title, url);
+
+               search_data->summary_missing = g_string_free (tmp, FALSE);
+               search_data->shell_extras = g_object_ref (shell_extras);
+               g_ptr_array_add (array_search_data, search_data);
+       }
+
+       gs_shell_extras_load (shell_extras, array_search_data);
+}
+
+void gs_shell_extras_search_mime_types (GsShellExtras *shell_extras, gchar **mime_types)
+{
+       _cleanup_ptrarray_unref_ GPtrArray *array_search_data = g_ptr_array_new_with_free_func 
((GDestroyNotify) search_data_free);
+       guint i;
+
+       for (i = 0; mime_types[i] != NULL; i++) {
+               GString *tmp;
+               SearchData *search_data;
+               _cleanup_free_ gchar *url = NULL;
+
+               search_data = g_slice_new0 (SearchData);
+               search_data->title = g_strdup_printf (_("%s MIME type"), mime_types[i]);
+               search_data->search = g_strdup (mime_types[i]);
+               search_data->url_not_found = g_strdup 
("https://fedoraproject.org/wiki/PackageKit_Items_Not_Found#Missing_MIME_Support";);
+
+               tmp = g_string_new ("");
+               /* TRANSLATORS: this is when we know about an application or
+                * addon, but it can't be listed for some reason */
+               g_string_append_printf (tmp, _("No applications are available for %s support."), 
search_data->title);
+               g_string_append (tmp, "\n");
+               /* TRANSLATORS: hyperlink title */
+               url = g_strdup_printf ("<a href=\"%s\">%s</a>",
+                                      search_data->url_not_found,
+                                      _("on the website"));
+               /* TRANSLATORS: first %s is the codec name, and second %s is a
+                 * hyperlink with the "on the website" text */
+               g_string_append_printf (tmp, _("Information about %s, as well as options "
+                                       "for how to get an application that can support this format "
+                                       "can be found %s."), search_data->title, url);
+
+               search_data->summary_missing = g_string_free (tmp, FALSE);
+               search_data->shell_extras = g_object_ref (shell_extras);
+               g_ptr_array_add (array_search_data, search_data);
+       }
+
+       gs_shell_extras_load (shell_extras, array_search_data);
+}
+
+void gs_shell_extras_search_fontconfig_resources (GsShellExtras *shell_extras, gchar **resources)
+{
+       _cleanup_ptrarray_unref_ GPtrArray *array_search_data = g_ptr_array_new_with_free_func 
((GDestroyNotify) search_data_free);
+       guint i;
+
+       for (i = 0; resources[i] != NULL; i++) {
+               GString *tmp;
+               SearchData *search_data;
+               _cleanup_free_ gchar *url = NULL;
+
+               search_data = g_slice_new0 (SearchData);
+               search_data->title = g_strdup (resources[i]); // XXX: Parse
+               search_data->search = g_strdup (resources[i]);
+               search_data->url_not_found = g_strdup 
("https://fedoraproject.org/wiki/PackageKit_Items_Not_Found#Missing_Font";);
+
+               tmp = g_string_new ("");
+               /* TRANSLATORS: this is when we know about an application or
+                * addon, but it can't be listed for some reason */
+               g_string_append_printf (tmp, _("No addon fonts are available for the %s format."), 
search_data->title);
+               g_string_append (tmp, "\n");
+               /* TRANSLATORS: hyperlink title */
+               url = g_strdup_printf ("<a href=\"%s\">%s</a>",
+                                      search_data->url_not_found,
+                                      _("on the website"));
+               /* TRANSLATORS: first %s is the codec name, and second %s is a
+                 * hyperlink with the "on the website" text */
+               g_string_append_printf (tmp, _("Information about %s, as well as options "
+                                       "for how to get a codec that can play this format "
+                                       "can be found %s."), search_data->title, url);
+
+               search_data->summary_missing = g_string_free (tmp, FALSE);
+               search_data->shell_extras = g_object_ref (shell_extras);
+               g_ptr_array_add (array_search_data, search_data);
+       }
+
+       gs_shell_extras_load (shell_extras, array_search_data);
+}
+
+void gs_shell_extras_search_gstreamer_resources (GsShellExtras *shell_extras, gchar **resources)
+{
+       _cleanup_ptrarray_unref_ GPtrArray *array_search_data = g_ptr_array_new_with_free_func 
((GDestroyNotify) search_data_free);
+       guint i;
+
+       for (i = 0; resources[i] != NULL; i++) {
+               GString *tmp;
+               SearchData *search_data;
+               _cleanup_strv_free_ gchar **parts = NULL;
+               _cleanup_free_ gchar *url = NULL;
+
+               parts = g_strsplit (resources[i], "|", 2);
+
+               search_data = g_slice_new0 (SearchData);
+               search_data->title = g_strdup (parts[0]);
+               search_data->search = g_strdup (parts[1]);
+               search_data->url_not_found = g_strdup 
("https://fedoraproject.org/wiki/PackageKit_Items_Not_Found#Missing_Codec";);
+
+               tmp = g_string_new ("");
+               /* TRANSLATORS: this is when we know about an application or
+                * addon, but it can't be listed for some reason */
+               g_string_append_printf (tmp, _("No addon codecs are available for the %s format."), 
search_data->title);
+               g_string_append (tmp, "\n");
+               /* TRANSLATORS: hyperlink title */
+               url = g_strdup_printf ("<a href=\"%s\">%s</a>",
+                                      search_data->url_not_found,
+                                      _("on the website"));
+               /* TRANSLATORS: first %s is the codec name, and second %s is a
+                 * hyperlink with the "on the website" text */
+               g_string_append_printf (tmp, _("Information about %s, as well as options "
+                                       "for how to get a codec that can play this format "
+                                       "can be found %s."), search_data->title, url);
+
+               search_data->summary_missing = g_string_free (tmp, FALSE);
+               search_data->shell_extras = g_object_ref (shell_extras);
+               g_ptr_array_add (array_search_data, search_data);
+       }
+
+       gs_shell_extras_load (shell_extras, array_search_data);
+}
+
+void gs_shell_extras_search_plasma_resources (GsShellExtras *shell_extras, gchar **resources)
+{
+       _cleanup_ptrarray_unref_ GPtrArray *array_search_data = g_ptr_array_new_with_free_func 
((GDestroyNotify) search_data_free);
+       guint i;
+
+       for (i = 0; resources[i] != NULL; i++) {
+               GString *tmp;
+               SearchData *search_data;
+               _cleanup_free_ gchar *url = NULL;
+
+               search_data = g_slice_new0 (SearchData);
+               search_data->title = g_strdup (resources[i]);
+               search_data->search = g_strdup (resources[i]);
+               search_data->url_not_found = g_strdup 
("https://fedoraproject.org/wiki/PackageKit_Items_Not_Found#Missing_Package";);
+
+               tmp = g_string_new ("");
+               /* TRANSLATORS: this is when we know about an application or
+                * addon, but it can't be listed for some reason */
+               g_string_append_printf (tmp, _("No addon plasma resources are available for the %s format."), 
search_data->title);
+               g_string_append (tmp, "\n");
+               /* TRANSLATORS: hyperlink title */
+               url = g_strdup_printf ("<a href=\"%s\">%s</a>",
+                                      search_data->url_not_found,
+                                      _("on the website"));
+               /* TRANSLATORS: first %s is the codec name, and second %s is a
+                 * hyperlink with the "on the website" text */
+               g_string_append_printf (tmp, _("Information about %s, as well as options "
+                                       "for how to get additional Plasma resources "
+                                       "can be found %s."), search_data->title, url);
+
+               search_data->summary_missing = g_string_free (tmp, FALSE);
+               search_data->shell_extras = g_object_ref (shell_extras);
+               g_ptr_array_add (array_search_data, search_data);
+       }
+
+       gs_shell_extras_load (shell_extras, array_search_data);
+}
+
+void gs_shell_extras_search_printer_drivers (GsShellExtras *shell_extras, gchar **device_ids)
+{
+       _cleanup_ptrarray_unref_ GPtrArray *array_search_data = g_ptr_array_new_with_free_func 
((GDestroyNotify) search_data_free);
+       guint i, j;
+       guint len;
+
+       len = g_strv_length (device_ids);
+       if (len > 1)
+               /* hardcode for now as we only support one at a time */
+               len = 1;
+
+       /* make a list of provides tags */
+       for (i = 0; i < len; i++) {
+               GString *tmp;
+               SearchData *search_data;
+               gchar *p;
+               guint n_fields;
+               _cleanup_free_ gchar *url = NULL;
+               _cleanup_free_ gchar *tag = NULL;
+               _cleanup_free_ gchar *mfg = NULL;
+               _cleanup_free_ gchar *mdl = NULL;
+               _cleanup_strv_free_ gchar **fields = NULL;
+
+               fields = g_strsplit (device_ids[i], ";", 0);
+               n_fields = g_strv_length (fields);
+               mfg = mdl = NULL;
+               for (j = 0; j < n_fields && (!mfg || !mdl); j++) {
+                       if (g_str_has_prefix (fields[j], "MFG:"))
+                               mfg = g_strdup (fields[j] + 4);
+                       else if (g_str_has_prefix (fields[j], "MDL:"))
+                               mdl = g_strdup (fields[j] + 4);
+               }
+
+               if (!mfg || !mdl) {
+                       g_warning("invalid line '%s', missing field",
+                                   device_ids[i]);
+                       continue;
+               }
+
+               tag = g_strconcat (mfg, ";", mdl, ";", NULL);
+
+               search_data = g_slice_new0 (SearchData);
+               search_data->title = g_strdup (tag);
+               search_data->search = g_ascii_strdown (tag, -1);
+
+               /* Replace spaces with underscores */
+               for (p = search_data->search; *p != '\0'; p++)
+                       if (*p == ' ')
+                               *p = '_';
+
+               search_data->url_not_found = g_strdup 
("https://fedoraproject.org/wiki/PackageKit_Items_Not_Found#Missing_Driver";);
+
+               tmp = g_string_new ("");
+               /* TRANSLATORS: this is when we know about an application or
+                * addon, but it can't be listed for some reason */
+               g_string_append_printf (tmp, _("No printer drivers are available for %s."), 
search_data->title);
+               g_string_append (tmp, "\n");
+               /* TRANSLATORS: hyperlink title */
+               url = g_strdup_printf ("<a href=\"%s\">%s</a>",
+                                      search_data->url_not_found,
+                                      _("on the website"));
+               /* TRANSLATORS: first %s is the codec name, and second %s is a
+                * hyperlink with the "on the website" text */
+               g_string_append_printf (tmp, _("Information about %s, as well as options "
+                                       "for how to get a driver that supports this printer "
+                                       "can be found %s."), search_data->title, url);
+
+               search_data->summary_missing = g_string_free (tmp, FALSE);
+               search_data->shell_extras = g_object_ref (shell_extras);
+               g_ptr_array_add (array_search_data, search_data);
+       }
+
+       gs_shell_extras_load (shell_extras, array_search_data);
+}
+
+void
+gs_shell_extras_switch_to (GsShellExtras *shell_extras,
+                           gboolean scroll_up)
+{
+       GsShellExtrasPrivate *priv = shell_extras->priv;
+       GtkWidget *widget;
+
+       if (gs_shell_get_mode (priv->shell) != GS_SHELL_MODE_EXTRAS) {
+               g_warning ("Called switch_to(codecs) when in mode %s",
+                          gs_shell_get_mode_string (priv->shell));
+               return;
+       }
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "application_details_header"));
+       gtk_widget_show (widget);
+
+       if (scroll_up) {
+               GtkAdjustment *adj;
+               adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow));
+               gtk_adjustment_set_value (adj, gtk_adjustment_get_lower (adj));
+       }
+
+       gs_shell_extras_update_ui_state (shell_extras);
+}
+
+static void
+row_activated_cb (GtkListBox *list_box,
+                  GtkListBoxRow *row,
+                  GsShellExtras *shell_extras)
+{
+       GsShellExtrasPrivate *priv = shell_extras->priv;
+       GsApp *app;
+
+       app = gs_app_row_get_app (GS_APP_ROW (row));
+
+       if (gs_app_get_kind (app) == GS_APP_KIND_MISSING &&
+           gs_app_get_url (app, AS_URL_KIND_MISSING) != NULL) {
+               gs_app_show_url (app, AS_URL_KIND_MISSING);
+       } else {
+               gs_shell_show_app (priv->shell, app);
+       }
+}
+
+static gchar *
+get_app_sort_key (GsApp *app)
+{
+       _cleanup_string_free_ GString *key;
+
+       key = g_string_sized_new (64);
+
+       /* sort missing applications as last */
+       switch (gs_app_get_kind (app)) {
+       case GS_APP_KIND_MISSING:
+               g_string_append (key, "9:");
+               break;
+       default:
+               g_string_append (key, "1:");
+               break;
+       }
+
+       /* finally, sort by short name */
+       g_string_append (key, gs_app_get_name (app));
+
+       return g_utf8_casefold (key->str, key->len);
+}
+
+static gint
+list_sort_func (GtkListBoxRow *a,
+                GtkListBoxRow *b,
+                gpointer user_data)
+{
+       GsApp *a1 = gs_app_row_get_app (GS_APP_ROW (a));
+       GsApp *a2 = gs_app_row_get_app (GS_APP_ROW (b));
+       _cleanup_free_ gchar *key1 = get_app_sort_key (a1);
+       _cleanup_free_ gchar *key2 = get_app_sort_key (a2);
+
+       /* compare the keys according to the algorithm above */
+       return g_strcmp0 (key1, key2);
+}
+
+static void
+list_header_func (GtkListBoxRow *row,
+                  GtkListBoxRow *before,
+                  gpointer user_data)
+{
+       GtkWidget *header;
+
+       /* first entry */
+       header = gtk_list_box_row_get_header (row);
+       if (before == NULL) {
+               gtk_list_box_row_set_header (row, NULL);
+               return;
+       }
+
+       /* already set */
+       if (header != NULL)
+               return;
+
+       /* set new */
+       header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+       gtk_list_box_row_set_header (row, header);
+}
+
+void
+gs_shell_extras_setup (GsShellExtras *shell_extras,
+                       GsShell *shell,
+                       GsPluginLoader *plugin_loader,
+                       GtkBuilder *builder,
+                       GCancellable *cancellable)
+{
+       GsShellExtrasPrivate *priv = shell_extras->priv;
+
+       g_return_if_fail (GS_IS_SHELL_EXTRAS (shell_extras));
+
+       priv->shell = shell;
+
+       priv->plugin_loader = g_object_ref (plugin_loader);
+       priv->builder = g_object_ref (builder);
+       priv->cancellable = g_object_ref (cancellable);
+
+       g_signal_connect (priv->list_box_results, "row-activated",
+                         G_CALLBACK (row_activated_cb), shell_extras);
+       gtk_list_box_set_header_func (GTK_LIST_BOX (priv->list_box_results),
+                                     list_header_func,
+                                     shell_extras, NULL);
+       gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->list_box_results),
+                                   list_sort_func,
+                                   shell_extras, NULL);
+
+       /* chain up */
+       gs_page_setup (GS_PAGE (shell_extras),
+                      shell,
+                      plugin_loader,
+                      cancellable);
+}
+
+static void
+gs_shell_extras_finalize (GObject *object)
+{
+       GsShellExtras *shell_extras = GS_SHELL_EXTRAS (object);
+       GsShellExtrasPrivate *priv = shell_extras->priv;
+
+       if (priv->search_cancellable != NULL) {
+               g_cancellable_cancel (priv->search_cancellable);
+               g_object_unref (priv->search_cancellable);
+       }
+
+       g_object_unref (priv->sizegroup_image);
+       g_object_unref (priv->sizegroup_name);
+       g_object_unref (priv->builder);
+       g_object_unref (priv->plugin_loader);
+       g_object_unref (priv->cancellable);
+       g_ptr_array_unref (priv->array_search_data);
+
+       G_OBJECT_CLASS (gs_shell_extras_parent_class)->finalize (object);
+}
+
+static void
+gs_shell_extras_init (GsShellExtras *shell_extras)
+{
+       gtk_widget_init_template (GTK_WIDGET (shell_extras));
+
+       shell_extras->priv = gs_shell_extras_get_instance_private (shell_extras);
+       shell_extras->priv->state = GS_SHELL_EXTRAS_STATE_LOADING;
+       shell_extras->priv->sizegroup_image = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+       shell_extras->priv->sizegroup_name = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+}
+
+static void
+gs_shell_extras_class_init (GsShellExtrasClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+       object_class->finalize = gs_shell_extras_finalize;
+
+       gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-shell-extras.ui");
+
+       gtk_widget_class_bind_template_child_private (widget_class, GsShellExtras, label_failed);
+       gtk_widget_class_bind_template_child_private (widget_class, GsShellExtras, label_no_results);
+       gtk_widget_class_bind_template_child_private (widget_class, GsShellExtras, list_box_results);
+       gtk_widget_class_bind_template_child_private (widget_class, GsShellExtras, scrolledwindow);
+       gtk_widget_class_bind_template_child_private (widget_class, GsShellExtras, spinner);
+       gtk_widget_class_bind_template_child_private (widget_class, GsShellExtras, stack);
+}
+
+GsShellExtras *
+gs_shell_extras_new (void)
+{
+       GsShellExtras *shell_extras;
+       shell_extras = g_object_new (GS_TYPE_SHELL_EXTRAS, NULL);
+       return GS_SHELL_EXTRAS (shell_extras);
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-shell-extras.h b/src/gs-shell-extras.h
new file mode 100644
index 0000000..21b5af9
--- /dev/null
+++ b/src/gs-shell-extras.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GS_SHELL_EXTRAS_H
+#define __GS_SHELL_EXTRAS_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "gs-page.h"
+#include "gs-plugin-loader.h"
+#include "gs-shell.h"
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_SHELL_EXTRAS           (gs_shell_extras_get_type ())
+#define GS_SHELL_EXTRAS(o)             (G_TYPE_CHECK_INSTANCE_CAST ((o), GS_TYPE_SHELL_EXTRAS, 
GsShellExtras))
+#define GS_SHELL_EXTRAS_CLASS(k)       (G_TYPE_CHECK_CLASS_CAST((k), GS_TYPE_SHELL_EXTRAS, 
GsShellExtrasClass))
+#define GS_IS_SHELL_EXTRAS(o)          (G_TYPE_CHECK_INSTANCE_TYPE ((o), GS_TYPE_SHELL_EXTRAS))
+#define GS_IS_SHELL_EXTRAS_CLASS(k)    (G_TYPE_CHECK_CLASS_TYPE ((k), GS_TYPE_SHELL_EXTRAS))
+#define GS_SHELL_EXTRAS_GET_CLASS(o)   (G_TYPE_INSTANCE_GET_CLASS ((o), GS_TYPE_SHELL_EXTRAS, 
GsShellExtrasClass))
+
+typedef struct GsShellExtrasPrivate GsShellExtrasPrivate;
+
+typedef struct
+{
+        GsPage                  parent;
+        GsShellExtrasPrivate   *priv;
+} GsShellExtras;
+
+typedef struct
+{
+       GsPageClass              parent_class;
+} GsShellExtrasClass;
+
+GType           gs_shell_extras_get_type                       (void);
+
+GsShellExtras  *gs_shell_extras_new                            (void);
+void            gs_shell_extras_search_package_files           (GsShellExtras          *shell_extras,
+                                                                gchar                  **files);
+void            gs_shell_extras_search_provide_files           (GsShellExtras          *shell_extras,
+                                                                gchar                  **files);
+void            gs_shell_extras_search_package_names           (GsShellExtras          *shell_extras,
+                                                                gchar                  **package_names);
+void            gs_shell_extras_search_mime_types              (GsShellExtras          *shell_extras,
+                                                                gchar                  **mime_types);
+void            gs_shell_extras_search_fontconfig_resources    (GsShellExtras          *shell_extras,
+                                                                gchar                  **resources);
+void            gs_shell_extras_search_gstreamer_resources     (GsShellExtras          *shell_extras,
+                                                                gchar                  **resources);
+void            gs_shell_extras_search_plasma_resources        (GsShellExtras          *shell_extras,
+                                                                gchar                  **resources);
+void            gs_shell_extras_search_printer_drivers         (GsShellExtras          *shell_extras,
+                                                                gchar                  **device_ids);
+void            gs_shell_extras_switch_to                      (GsShellExtras          *shell_extras,
+                                                                gboolean                scroll_up);
+void            gs_shell_extras_reload                         (GsShellExtras          *shell_extras);
+void            gs_shell_extras_setup                          (GsShellExtras          *shell_extras,
+                                                                GsShell                *shell,
+                                                                GsPluginLoader         *plugin_loader,
+                                                                GtkBuilder             *builder,
+                                                                GCancellable           *cancellable);
+
+G_END_DECLS
+
+#endif /* __GS_SHELL_EXTRAS_H */
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-shell-extras.ui b/src/gs-shell-extras.ui
new file mode 100644
index 0000000..b25a829
--- /dev/null
+++ b/src/gs-shell-extras.ui
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.10"/>
+  <template class="GsShellExtras" parent="GsPage">
+    <child internal-child="accessible">
+      <object class="AtkObject" id="codecs-accessible">
+        <property name="accessible-name" translatable="yes">Codecs page</property>
+      </object>
+    </child>
+    <child>
+      <object class="GtkStack" id="stack">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkBox" id="box_spinner">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">12</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+            <child>
+              <object class="GtkSpinner" id="spinner">
+                <property name="visible">True</property>
+                <property name="width_request">32</property>
+                <property name="height_request">32</property>
+                <property name="halign">center</property>
+                <property name="valign">center</property>
+                <property name="hexpand">True</property>
+                <property name="vexpand">True</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">spinner</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolledwindow">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="hscrollbar_policy">never</property>
+            <property name="vscrollbar_policy">automatic</property>
+            <property name="shadow_type">none</property>
+            <style>
+              <class name="main-scrolled-software"/>
+            </style>
+            <child>
+              <object class="GtkBox" id="box_results">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkListBox" id="list_box_results">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="selection_mode">none</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkSeparator" id="separator_results">
+                    <property name="visible">True</property>
+                    <property name="orientation">horizontal</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">results</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box_no_results">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">24</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <child>
+              <object class="GtkImage" id="image_no_results">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="pixel_size">64</property>
+                <property name="icon_name">face-sad-symbolic</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label_no_results">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="use_markup">True</property>
+                <property name="wrap">True</property>
+                <property name="max_width_chars">60</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">no-results</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box_failed">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">12</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+            <child>
+              <object class="GtkImage" id="image_failed">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="pixel_size">128</property>
+                <property name="icon_name">action-unavailable-symbolic</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label_failed">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="wrap">True</property>
+                <property name="max-width-chars">60</property>
+                <attributes>
+                  <attribute name="scale" value="1.4"/>
+                </attributes>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">failed</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/gs-shell.c b/src/gs-shell.c
index e5647e0..96525ab 100644
--- a/src/gs-shell.c
+++ b/src/gs-shell.c
@@ -34,6 +34,7 @@
 #include "gs-shell-overview.h"
 #include "gs-shell-updates.h"
 #include "gs-shell-category.h"
+#include "gs-shell-extras.h"
 #include "gs-sources-dialog.h"
 #include "gs-update-dialog.h"
 
@@ -44,6 +45,7 @@ static const gchar *page_name[] = {
        "updates",
        "details",
        "category",
+       "extras",
 };
 
 static void    gs_shell_finalize       (GObject        *object);
@@ -67,6 +69,7 @@ struct GsShellPrivate
        GsShellUpdates          *shell_updates;
        GsShellDetails          *shell_details;
        GsShellCategory         *shell_category;
+       GsShellExtras           *shell_extras;
        GtkBuilder              *builder;
        GtkWindow               *main_window;
        GQueue                  *back_entry_stack;
@@ -112,7 +115,7 @@ gs_shell_activate (GsShell *shell)
        gtk_window_present (priv->main_window);
 }
 
-static void
+void
 gs_shell_change_mode (GsShell *shell,
                      GsShellMode mode,
                      GsApp *app,
@@ -207,6 +210,9 @@ gs_shell_change_mode (GsShell *shell,
                                                GS_CATEGORY (data));
                gs_shell_category_switch_to (priv->shell_category);
                break;
+       case GS_SHELL_MODE_EXTRAS:
+               gs_shell_extras_switch_to (priv->shell_extras, scroll_up);
+               break;
        default:
                g_assert_not_reached ();
        }
@@ -492,6 +498,7 @@ gs_shell_updates_changed_cb (GsPluginLoader *plugin_loader, GsShell *shell)
 {
        GsShellPrivate *priv = shell->priv;
        gs_shell_category_reload (priv->shell_category);
+       gs_shell_extras_reload (priv->shell_extras);
        gs_shell_details_reload (priv->shell_details);
        gs_shell_installed_reload (priv->shell_installed);
        gs_shell_overview_reload (priv->shell_overview);
@@ -609,6 +616,12 @@ gs_shell_setup (GsShell *shell, GsPluginLoader *plugin_loader, GCancellable *can
                                 priv->plugin_loader,
                                 priv->builder,
                                 priv->cancellable);
+       priv->shell_extras = GS_SHELL_EXTRAS (gtk_builder_get_object (priv->builder, "shell_extras"));
+       gs_shell_extras_setup (priv->shell_extras,
+                                shell,
+                                priv->plugin_loader,
+                                priv->builder,
+                                priv->cancellable);
 
        /* set up search */
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "entry_search"));
@@ -746,6 +759,86 @@ gs_shell_show_category (GsShell *shell, GsCategory *category)
        gs_shell_change_mode (shell, GS_SHELL_MODE_CATEGORY, NULL, category, TRUE);
 }
 
+void gs_shell_show_extras_search_package_files (GsShell *shell, gchar **files)
+{
+       GsShellPrivate *priv = shell->priv;
+
+       save_back_entry (shell);
+       gs_shell_extras_search_package_files (priv->shell_extras, files);
+       gs_shell_change_mode (shell, GS_SHELL_MODE_EXTRAS, NULL, NULL, TRUE);
+       gs_shell_activate (shell);
+}
+
+void gs_shell_show_extras_search_provide_files (GsShell *shell, gchar **files)
+{
+       GsShellPrivate *priv = shell->priv;
+
+       save_back_entry (shell);
+       gs_shell_extras_search_provide_files (priv->shell_extras, files);
+       gs_shell_change_mode (shell, GS_SHELL_MODE_EXTRAS, NULL, NULL, TRUE);
+       gs_shell_activate (shell);
+}
+
+void gs_shell_show_extras_search_package_names (GsShell *shell, gchar **package_names)
+{
+       GsShellPrivate *priv = shell->priv;
+
+       save_back_entry (shell);
+       gs_shell_extras_search_package_names (priv->shell_extras, package_names);
+       gs_shell_change_mode (shell, GS_SHELL_MODE_EXTRAS, NULL, NULL, TRUE);
+       gs_shell_activate (shell);
+}
+
+void gs_shell_show_extras_search_mime_types (GsShell *shell, gchar **mime_types)
+{
+       GsShellPrivate *priv = shell->priv;
+
+       save_back_entry (shell);
+       gs_shell_extras_search_mime_types (priv->shell_extras, mime_types);
+       gs_shell_change_mode (shell, GS_SHELL_MODE_EXTRAS, NULL, NULL, TRUE);
+       gs_shell_activate (shell);
+}
+
+void gs_shell_show_extras_search_fontconfig_resources (GsShell *shell, gchar **resources)
+{
+       GsShellPrivate *priv = shell->priv;
+
+       save_back_entry (shell);
+       gs_shell_extras_search_fontconfig_resources (priv->shell_extras, resources);
+       gs_shell_change_mode (shell, GS_SHELL_MODE_EXTRAS, NULL, NULL, TRUE);
+       gs_shell_activate (shell);
+}
+
+void gs_shell_show_extras_search_gstreamer_resources (GsShell *shell, gchar **resources)
+{
+       GsShellPrivate *priv = shell->priv;
+
+       save_back_entry (shell);
+       gs_shell_extras_search_gstreamer_resources (priv->shell_extras, resources);
+       gs_shell_change_mode (shell, GS_SHELL_MODE_EXTRAS, NULL, NULL, TRUE);
+       gs_shell_activate (shell);
+}
+
+void gs_shell_show_extras_search_plasma_resources (GsShell *shell, gchar **resources)
+{
+       GsShellPrivate *priv = shell->priv;
+
+       save_back_entry (shell);
+       gs_shell_extras_search_plasma_resources (priv->shell_extras, resources);
+       gs_shell_change_mode (shell, GS_SHELL_MODE_EXTRAS, NULL, NULL, TRUE);
+       gs_shell_activate (shell);
+}
+
+void gs_shell_show_extras_search_printer_drivers (GsShell *shell, gchar **device_ids)
+{
+       GsShellPrivate *priv = shell->priv;
+
+       save_back_entry (shell);
+       gs_shell_extras_search_printer_drivers (priv->shell_extras, device_ids);
+       gs_shell_change_mode (shell, GS_SHELL_MODE_EXTRAS, NULL, NULL, TRUE);
+       gs_shell_activate (shell);
+}
+
 void
 gs_shell_show_search (GsShell *shell, const gchar *search)
 {
diff --git a/src/gs-shell.h b/src/gs-shell.h
index fdf8691..248c928 100644
--- a/src/gs-shell.h
+++ b/src/gs-shell.h
@@ -60,6 +60,7 @@ typedef enum {
        GS_SHELL_MODE_UPDATES,
        GS_SHELL_MODE_DETAILS,
        GS_SHELL_MODE_CATEGORY,
+       GS_SHELL_MODE_EXTRAS,
        GS_SHELL_MODE_LAST
 } GsShellMode;
 
@@ -69,6 +70,11 @@ GsShell              *gs_shell_new                   (void);
 void            gs_shell_activate              (GsShell        *shell);
 void            gs_shell_refresh               (GsShell        *shell,
                                                 GCancellable   *cancellable);
+void            gs_shell_change_mode           (GsShell        *shell,
+                                                GsShellMode     mode,
+                                                GsApp          *app,
+                                                gpointer        data,
+                                                gboolean        scroll_up);
 void            gs_shell_set_mode              (GsShell        *shell,
                                                 GsShellMode     mode);
 GsShellMode     gs_shell_get_mode              (GsShell        *shell);
@@ -88,6 +94,22 @@ void          gs_shell_show_search_result    (GsShell        *shell,
                                                 const gchar    *search);
 void            gs_shell_show_details          (GsShell        *shell,
                                                 const gchar    *id);
+void            gs_shell_show_extras_search_package_files              (GsShell        *shell,
+                                                                        gchar          **files);
+void            gs_shell_show_extras_search_provide_files              (GsShell        *shell,
+                                                                        gchar          **files);
+void            gs_shell_show_extras_search_package_names              (GsShell        *shell,
+                                                                        gchar          **package_names);
+void            gs_shell_show_extras_search_mime_types                 (GsShell        *shell,
+                                                                        gchar          **mime_types);
+void            gs_shell_show_extras_search_fontconfig_resources       (GsShell        *shell,
+                                                                        gchar          **resources);
+void            gs_shell_show_extras_search_gstreamer_resources        (GsShell        *shell,
+                                                                        gchar          **resources);
+void            gs_shell_show_extras_search_plasma_resources           (GsShell        *shell,
+                                                                        gchar          **resources);
+void            gs_shell_show_extras_search_printer_drivers            (GsShell        *shell,
+                                                                        gchar          **device_ids);
 void            gs_shell_setup                 (GsShell        *shell,
                                                 GsPluginLoader *plugin_loader,
                                                 GCancellable   *cancellable);
diff --git a/src/plugins/gs-plugin-packagekit.c b/src/plugins/gs-plugin-packagekit.c
index c4c81a3..12ef1f9 100644
--- a/src/plugins/gs-plugin-packagekit.c
+++ b/src/plugins/gs-plugin-packagekit.c
@@ -569,3 +569,65 @@ gs_plugin_app_remove (GsPlugin *plugin,
        }
        return TRUE;
 }
+
+/**
+ * gs_plugin_add_search_files:
+ */
+gboolean
+gs_plugin_add_search_files (GsPlugin *plugin,
+                            gchar **search,
+                            GList **list,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       PkBitfield filter;
+       _cleanup_object_unref_ PkResults *results = NULL;
+
+       /* do sync call */
+       gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_WAITING);
+       filter = pk_bitfield_from_enums (PK_FILTER_ENUM_NEWEST,
+                                        PK_FILTER_ENUM_ARCH,
+                                        -1);
+       results = pk_client_search_files (PK_CLIENT (plugin->priv->task),
+                                         filter,
+                                         search,
+                                         cancellable,
+                                         gs_plugin_packagekit_progress_cb, plugin,
+                                         error);
+       if (results == NULL)
+               return FALSE;
+
+       /* add results */
+       return gs_plugin_packagekit_add_results (plugin, list, results, error);
+}
+
+/**
+ * gs_plugin_add_search_what_provides:
+ */
+gboolean
+gs_plugin_add_search_what_provides (GsPlugin *plugin,
+                                    gchar **search,
+                                    GList **list,
+                                    GCancellable *cancellable,
+                                    GError **error)
+{
+       PkBitfield filter;
+       _cleanup_object_unref_ PkResults *results = NULL;
+
+       /* do sync call */
+       gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_WAITING);
+       filter = pk_bitfield_from_enums (PK_FILTER_ENUM_NEWEST,
+                                        PK_FILTER_ENUM_ARCH,
+                                        -1);
+       results = pk_client_what_provides (PK_CLIENT (plugin->priv->task),
+                                          filter,
+                                          search,
+                                          cancellable,
+                                          gs_plugin_packagekit_progress_cb, plugin,
+                                          error);
+       if (results == NULL)
+               return FALSE;
+
+       /* add results */
+       return gs_plugin_packagekit_add_results (plugin, list, results, error);
+}


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