[gnome-software/wip/kalev/codecs: 11/12] Add a new Extras page for displaying PK session service results
- From: Kalev Lember <klember src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/wip/kalev/codecs: 11/12] Add a new Extras page for displaying PK session service results
- Date: Tue, 10 Feb 2015 12:14:07 +0000 (UTC)
commit d4a02f81e5328d865903955ee925e8c3533b2a05
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, ×tamp);
+
+ 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, ×tamp);
+
+ 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, ×tamp);
+
+ 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, ×tamp);
+
+ 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, ×tamp);
+
+ 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, ×tamp);
+
+ 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, ×tamp);
+
+ 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, ×tamp);
+
+ 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 ccb8fb8..f6da06a 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,
@@ -356,6 +780,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]