summaryrefslogtreecommitdiffstats
path: root/search-provider/cc-search-provider.c
diff options
context:
space:
mode:
Diffstat (limited to 'search-provider/cc-search-provider.c')
-rw-r--r--search-provider/cc-search-provider.c377
1 files changed, 377 insertions, 0 deletions
diff --git a/search-provider/cc-search-provider.c b/search-provider/cc-search-provider.c
new file mode 100644
index 0000000..8a8981a
--- /dev/null
+++ b/search-provider/cc-search-provider.c
@@ -0,0 +1,377 @@
+/*
+ * Copyright (c) 2012 Giovanni Campagna <scampa.giovanni@gmail.com>
+ *
+ * The Control Center 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.
+ *
+ * The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gio/gdesktopappinfo.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+#include <shell/cc-panel.h>
+#include <shell/cc-shell-model.h>
+#include <shell/cc-panel-loader.h>
+
+#include "cc-util.h"
+
+#include "control-center-search-provider.h"
+#include "cc-search-provider.h"
+
+struct _CcSearchProvider
+{
+ GObject parent;
+
+ CcShellSearchProvider2 *skeleton;
+
+ GHashTable *iter_table; /* COL_ID -> GtkTreeIter */
+};
+
+typedef enum {
+ MATCH_NONE,
+ MATCH_PREFIX,
+ MATCH_SUBSTRING
+} PanelSearchMatch;
+
+G_DEFINE_TYPE (CcSearchProvider, cc_search_provider, G_TYPE_OBJECT)
+
+static char **
+get_casefolded_terms (char **terms)
+{
+ char **casefolded_terms;
+ int i, n;
+
+ n = g_strv_length ((char**) terms);
+ casefolded_terms = g_new (char*, n + 1);
+
+ for (i = 0; i < n; i++)
+ casefolded_terms[i] = cc_util_normalize_casefold_and_unaccent (terms[i]);
+ casefolded_terms[n] = NULL;
+
+ return casefolded_terms;
+}
+
+static gboolean
+matches_all_terms (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ char **terms)
+{
+ int i;
+
+ for (i = 0; terms[i]; i++)
+ {
+ if (!cc_shell_model_iter_matches_search (CC_SHELL_MODEL (model),
+ iter,
+ terms[i]))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static GtkTreeModel *
+get_model (void)
+{
+ CcSearchProviderApp *app;
+
+ app = cc_search_provider_app_get ();
+ return GTK_TREE_MODEL (cc_search_provider_app_get_model (app));
+}
+
+static gchar **
+get_results (gchar **terms)
+{
+ g_auto(GStrv) casefolded_terms = NULL;
+ GtkTreeModel *model = get_model ();
+ GtkTreeIter iter;
+ GPtrArray *results;
+ gboolean ok;
+
+ casefolded_terms = get_casefolded_terms (terms);
+ results = g_ptr_array_new ();
+
+ cc_shell_model_set_sort_terms (CC_SHELL_MODEL (model), casefolded_terms);
+
+ ok = gtk_tree_model_get_iter_first (model, &iter);
+ while (ok)
+ {
+ if (matches_all_terms (model, &iter, casefolded_terms))
+ {
+ gchar *id;
+ gtk_tree_model_get (model, &iter, COL_ID, &id, -1);
+ g_ptr_array_add (results, id);
+ }
+
+ ok = gtk_tree_model_iter_next (model, &iter);
+ }
+
+ g_ptr_array_add (results, NULL);
+
+ return (char**) g_ptr_array_free (results, FALSE);
+}
+
+static gboolean
+handle_get_initial_result_set (CcShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ char **terms,
+ CcSearchProvider *self)
+{
+ g_auto(GStrv) results = get_results (terms);
+ cc_shell_search_provider2_complete_get_initial_result_set (skeleton,
+ invocation,
+ (const char* const*) results);
+ return TRUE;
+}
+
+static gboolean
+handle_get_subsearch_result_set (CcShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ char **previous_results,
+ char **terms,
+ CcSearchProvider *self)
+{
+ /* We ignore the previous results here since the model re-sorts for
+ * the new terms. This means that we're not really doing a subsearch
+ * but, on the other hand, the results are consistent with the
+ * control center's own search. In any case, the number of elements
+ * in the model is always small enough that we don't need to worry
+ * about this taking too long.
+ */
+ g_auto(GStrv) results = get_results (terms);
+ cc_shell_search_provider2_complete_get_subsearch_result_set (skeleton,
+ invocation,
+ (const char* const*) results);
+ return TRUE;
+}
+
+static GtkTreeIter *
+get_iter_for_result (CcSearchProvider *self,
+ const gchar *result)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean ok;
+ gchar *id;
+
+ /* Caching GtkTreeIters in this way is only OK because the model is
+ * a GtkListStore which guarantees that while a row exists, the iter
+ * is persistent.
+ */
+ if (self->iter_table)
+ goto lookup;
+
+ self->iter_table = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) gtk_tree_iter_free);
+
+ model = get_model ();
+ ok = gtk_tree_model_get_iter_first (model, &iter);
+ while (ok)
+ {
+ gtk_tree_model_get (model, &iter, COL_ID, &id, -1);
+
+ g_hash_table_replace (self->iter_table, id, gtk_tree_iter_copy (&iter));
+
+ ok = gtk_tree_model_iter_next (model, &iter);
+ }
+
+ lookup:
+ return g_hash_table_lookup (self->iter_table, result);
+}
+
+static gboolean
+handle_get_result_metas (CcShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ char **results,
+ CcSearchProvider *self)
+{
+ GtkTreeModel *model = get_model ();
+ GtkTreeIter *iter;
+ int i;
+ GVariantBuilder builder;
+ const char *id;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
+
+ for (i = 0; results[i]; i++)
+ {
+ g_autofree gchar *description = NULL;
+ g_autofree gchar *name = NULL;
+ g_autoptr(GAppInfo) app = NULL;
+ g_autoptr(GIcon) icon = NULL;
+
+ iter = get_iter_for_result (self, results[i]);
+ if (!iter)
+ continue;
+
+ gtk_tree_model_get (model, iter,
+ COL_APP, &app,
+ COL_NAME, &name,
+ COL_GICON, &icon,
+ COL_DESCRIPTION, &description,
+ -1);
+ id = g_app_info_get_id (app);
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (&builder, "{sv}",
+ "id", g_variant_new_string (id));
+ g_variant_builder_add (&builder, "{sv}",
+ "name", g_variant_new_string (name));
+ g_variant_builder_add (&builder, "{sv}",
+ "icon", g_icon_serialize (icon));
+ g_variant_builder_add (&builder, "{sv}",
+ "description", g_variant_new_string (description));
+ g_variant_builder_close (&builder);
+ }
+
+ cc_shell_search_provider2_complete_get_result_metas (skeleton,
+ invocation,
+ g_variant_builder_end (&builder));
+ return TRUE;
+}
+
+static gboolean
+handle_activate_result (CcShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ char *identifier,
+ char **results,
+ guint timestamp,
+ CcSearchProvider *self)
+{
+ GdkAppLaunchContext *launch_context;
+ g_autoptr(GError) error = NULL;
+ GAppInfo *app;
+
+ launch_context = gdk_display_get_app_launch_context (gdk_display_get_default ());
+ gdk_app_launch_context_set_timestamp (launch_context, timestamp);
+
+ app = G_APP_INFO (g_desktop_app_info_new (identifier));
+
+ if (!g_app_info_launch (app, NULL, G_APP_LAUNCH_CONTEXT (launch_context), &error))
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ else
+ cc_shell_search_provider2_complete_activate_result (skeleton, invocation);
+
+ return TRUE;
+}
+
+static gboolean
+handle_launch_search (CcShellSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ char **terms,
+ guint timestamp,
+ CcSearchProvider *self)
+{
+ GdkAppLaunchContext *launch_context;
+ g_autoptr(GError) error = NULL;
+ char *joined_terms, *command_line;
+ GAppInfo *app;
+
+ launch_context = gdk_display_get_app_launch_context (gdk_display_get_default ());
+ gdk_app_launch_context_set_timestamp (launch_context, timestamp);
+
+ joined_terms = g_strjoinv (" ", terms);
+ command_line = g_strdup_printf ("gnome-control-center -s '%s'", joined_terms);
+ app = g_app_info_create_from_commandline (command_line,
+ "gnome-control-center.desktop",
+ G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION,
+ &error);
+ if (!app)
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return TRUE;
+ }
+
+ if (!g_app_info_launch (app, NULL, G_APP_LAUNCH_CONTEXT (launch_context), &error))
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ else
+ cc_shell_search_provider2_complete_launch_search (skeleton, invocation);
+
+ return TRUE;
+}
+
+static void
+cc_search_provider_init (CcSearchProvider *self)
+{
+ self->skeleton = cc_shell_search_provider2_skeleton_new ();
+
+ g_signal_connect (self->skeleton, "handle-get-initial-result-set",
+ G_CALLBACK (handle_get_initial_result_set), self);
+ g_signal_connect (self->skeleton, "handle-get-subsearch-result-set",
+ G_CALLBACK (handle_get_subsearch_result_set), self);
+ g_signal_connect (self->skeleton, "handle-get-result-metas",
+ G_CALLBACK (handle_get_result_metas), self);
+ g_signal_connect (self->skeleton, "handle-activate-result",
+ G_CALLBACK (handle_activate_result), self);
+ g_signal_connect (self->skeleton, "handle-launch-search",
+ G_CALLBACK (handle_launch_search), self);
+}
+
+gboolean
+cc_search_provider_dbus_register (CcSearchProvider *self,
+ GDBusConnection *connection,
+ const gchar *object_path,
+ GError **error)
+{
+ GDBusInterfaceSkeleton *skeleton;
+
+ skeleton = G_DBUS_INTERFACE_SKELETON (self->skeleton);
+
+ return g_dbus_interface_skeleton_export (skeleton, connection, object_path, error);
+}
+
+void
+cc_search_provider_dbus_unregister (CcSearchProvider *self,
+ GDBusConnection *connection,
+ const gchar *object_path)
+{
+ GDBusInterfaceSkeleton *skeleton;
+
+ skeleton = G_DBUS_INTERFACE_SKELETON (self->skeleton);
+
+ if (g_dbus_interface_skeleton_has_connection (skeleton, connection))
+ g_dbus_interface_skeleton_unexport_from_connection (skeleton, connection);
+}
+
+static void
+cc_search_provider_dispose (GObject *object)
+{
+ CcSearchProvider *self;
+
+ self = CC_SEARCH_PROVIDER (object);
+
+ g_clear_object (&self->skeleton);
+ g_clear_pointer (&self->iter_table, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (cc_search_provider_parent_class)->dispose (object);
+}
+
+static void
+cc_search_provider_class_init (CcSearchProviderClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = cc_search_provider_dispose;
+}
+
+CcSearchProvider *
+cc_search_provider_new (void)
+{
+ return g_object_new (CC_TYPE_SEARCH_PROVIDER, NULL);
+}
+