GCC Code Coverage Report


Directory: ./
File: search-provider/cc-search-provider.c
Date: 2024-05-03 09:46:52
Exec Total Coverage
Lines: 0 123 0.0%
Functions: 0 19 0.0%
Branches: 0 45 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2012 Giovanni Campagna <scampa.giovanni@gmail.com>
3 *
4 * The Control Center is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * The Control Center is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with the Control Center; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 *
18 */
19
20 #include <config.h>
21
22 #include <glib/gi18n.h>
23 #include <gio/gio.h>
24 #include <gio/gdesktopappinfo.h>
25 #include <gtk/gtk.h>
26 #include <string.h>
27
28 #include <shell/cc-panel.h>
29 #include <shell/cc-shell-model.h>
30 #include <shell/cc-panel-loader.h>
31
32 #include "cc-util.h"
33
34 #include "control-center-search-provider.h"
35 #include "cc-search-provider.h"
36
37 struct _CcSearchProvider
38 {
39 GObject parent;
40
41 CcShellSearchProvider2 *skeleton;
42
43 GHashTable *iter_table; /* COL_ID -> GtkTreeIter */
44 };
45
46 typedef enum {
47 MATCH_NONE,
48 MATCH_PREFIX,
49 MATCH_SUBSTRING
50 } PanelSearchMatch;
51
52 G_DEFINE_TYPE (CcSearchProvider, cc_search_provider, G_TYPE_OBJECT)
53
54 static char **
55 get_casefolded_terms (char **terms)
56 {
57 char **casefolded_terms;
58 int i, n;
59
60 n = g_strv_length ((char**) terms);
61 casefolded_terms = g_new (char*, n + 1);
62
63 for (i = 0; i < n; i++)
64 casefolded_terms[i] = cc_util_normalize_casefold_and_unaccent (terms[i]);
65 casefolded_terms[n] = NULL;
66
67 return casefolded_terms;
68 }
69
70 static gboolean
71 matches_all_terms (GtkTreeModel *model,
72 GtkTreeIter *iter,
73 char **terms)
74 {
75 int i;
76
77 for (i = 0; terms[i]; i++)
78 {
79 if (!cc_shell_model_iter_matches_search (CC_SHELL_MODEL (model),
80 iter,
81 terms[i]))
82 return FALSE;
83 }
84
85 return TRUE;
86 }
87
88 static GtkTreeModel *
89 get_model (void)
90 {
91 CcSearchProviderApp *app;
92
93 app = cc_search_provider_app_get ();
94 return GTK_TREE_MODEL (cc_search_provider_app_get_model (app));
95 }
96
97 static gchar **
98 get_results (gchar **terms)
99 {
100 g_auto(GStrv) casefolded_terms = NULL;
101 GtkTreeModel *model = get_model ();
102 GtkTreeIter iter;
103 GPtrArray *results;
104 gboolean ok;
105
106 casefolded_terms = get_casefolded_terms (terms);
107 results = g_ptr_array_new ();
108
109 cc_shell_model_set_sort_terms (CC_SHELL_MODEL (model), casefolded_terms);
110
111 ok = gtk_tree_model_get_iter_first (model, &iter);
112 while (ok)
113 {
114 if (matches_all_terms (model, &iter, casefolded_terms))
115 {
116 gchar *id;
117 gtk_tree_model_get (model, &iter, COL_ID, &id, -1);
118 g_ptr_array_add (results, id);
119 }
120
121 ok = gtk_tree_model_iter_next (model, &iter);
122 }
123
124 g_ptr_array_add (results, NULL);
125
126 return (char**) g_ptr_array_free (results, FALSE);
127 }
128
129 static gboolean
130 handle_get_initial_result_set (CcSearchProvider *self,
131 GDBusMethodInvocation *invocation,
132 char **terms)
133 {
134 g_auto(GStrv) results = get_results (terms);
135 cc_shell_search_provider2_complete_get_initial_result_set (self->skeleton,
136 invocation,
137 (const char* const*) results);
138 return TRUE;
139 }
140
141 static gboolean
142 handle_get_subsearch_result_set (CcSearchProvider *self,
143 GDBusMethodInvocation *invocation,
144 char **previous_results,
145 char **terms)
146 {
147 /* We ignore the previous results here since the model re-sorts for
148 * the new terms. This means that we're not really doing a subsearch
149 * but, on the other hand, the results are consistent with the
150 * control center's own search. In any case, the number of elements
151 * in the model is always small enough that we don't need to worry
152 * about this taking too long.
153 */
154 g_auto(GStrv) results = get_results (terms);
155 cc_shell_search_provider2_complete_get_subsearch_result_set (self->skeleton,
156 invocation,
157 (const char* const*) results);
158 return TRUE;
159 }
160
161 static GtkTreeIter *
162 get_iter_for_result (CcSearchProvider *self,
163 const gchar *result)
164 {
165 GtkTreeModel *model;
166 GtkTreeIter iter;
167 gboolean ok;
168 gchar *id;
169
170 /* Caching GtkTreeIters in this way is only OK because the model is
171 * a GtkListStore which guarantees that while a row exists, the iter
172 * is persistent.
173 */
174 if (self->iter_table)
175 goto lookup;
176
177 self->iter_table = g_hash_table_new_full (g_str_hash, g_str_equal,
178 g_free, (GDestroyNotify) gtk_tree_iter_free);
179
180 model = get_model ();
181 ok = gtk_tree_model_get_iter_first (model, &iter);
182 while (ok)
183 {
184 gtk_tree_model_get (model, &iter, COL_ID, &id, -1);
185
186 g_hash_table_replace (self->iter_table, id, gtk_tree_iter_copy (&iter));
187
188 ok = gtk_tree_model_iter_next (model, &iter);
189 }
190
191 lookup:
192 return g_hash_table_lookup (self->iter_table, result);
193 }
194
195 static gboolean
196 handle_get_result_metas (CcSearchProvider *self,
197 GDBusMethodInvocation *invocation,
198 char **results)
199 {
200 GtkTreeModel *model = get_model ();
201 GtkTreeIter *iter;
202 int i;
203 GVariantBuilder builder;
204 const char *id;
205
206 g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
207
208 for (i = 0; results[i]; i++)
209 {
210 g_autofree gchar *description = NULL;
211 g_autofree gchar *name = NULL;
212 g_autoptr(GAppInfo) app = NULL;
213 g_autoptr(GIcon) icon = NULL;
214
215 iter = get_iter_for_result (self, results[i]);
216 if (!iter)
217 continue;
218
219 gtk_tree_model_get (model, iter,
220 COL_APP, &app,
221 COL_NAME, &name,
222 COL_GICON, &icon,
223 COL_DESCRIPTION, &description,
224 -1);
225 id = g_app_info_get_id (app);
226
227 g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
228 g_variant_builder_add (&builder, "{sv}",
229 "id", g_variant_new_string (id));
230 g_variant_builder_add (&builder, "{sv}",
231 "name", g_variant_new_string (name));
232 g_variant_builder_add (&builder, "{sv}",
233 "icon", g_icon_serialize (icon));
234 g_variant_builder_add (&builder, "{sv}",
235 "description", g_variant_new_string (description));
236 g_variant_builder_close (&builder);
237 }
238
239 cc_shell_search_provider2_complete_get_result_metas (self->skeleton,
240 invocation,
241 g_variant_builder_end (&builder));
242 return TRUE;
243 }
244
245 static gboolean
246 handle_activate_result (CcSearchProvider *self,
247 GDBusMethodInvocation *invocation,
248 char *identifier,
249 char **results,
250 guint timestamp)
251 {
252 GdkAppLaunchContext *launch_context;
253 g_autoptr(GError) error = NULL;
254 GAppInfo *app;
255
256 launch_context = gdk_display_get_app_launch_context (gdk_display_get_default ());
257 gdk_app_launch_context_set_timestamp (launch_context, timestamp);
258
259 app = G_APP_INFO (g_desktop_app_info_new (identifier));
260
261 if (!g_app_info_launch (app, NULL, G_APP_LAUNCH_CONTEXT (launch_context), &error))
262 g_dbus_method_invocation_return_gerror (invocation, error);
263 else
264 cc_shell_search_provider2_complete_activate_result (self->skeleton, invocation);
265
266 return TRUE;
267 }
268
269 static gboolean
270 handle_launch_search (CcSearchProvider *self,
271 GDBusMethodInvocation *invocation,
272 char **terms,
273 guint timestamp)
274 {
275 GdkAppLaunchContext *launch_context;
276 g_autoptr(GError) error = NULL;
277 char *joined_terms, *command_line;
278 GAppInfo *app;
279
280 launch_context = gdk_display_get_app_launch_context (gdk_display_get_default ());
281 gdk_app_launch_context_set_timestamp (launch_context, timestamp);
282
283 joined_terms = g_strjoinv (" ", terms);
284 command_line = g_strdup_printf ("gnome-control-center -s '%s'", joined_terms);
285 app = g_app_info_create_from_commandline (command_line,
286 "gnome-control-center.desktop",
287 G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION,
288 &error);
289 if (!app)
290 {
291 g_dbus_method_invocation_return_gerror (invocation, error);
292 return TRUE;
293 }
294
295 if (!g_app_info_launch (app, NULL, G_APP_LAUNCH_CONTEXT (launch_context), &error))
296 g_dbus_method_invocation_return_gerror (invocation, error);
297 else
298 cc_shell_search_provider2_complete_launch_search (self->skeleton, invocation);
299
300 return TRUE;
301 }
302
303 static void
304 cc_search_provider_init (CcSearchProvider *self)
305 {
306 self->skeleton = cc_shell_search_provider2_skeleton_new ();
307
308 g_signal_connect_swapped (self->skeleton, "handle-get-initial-result-set",
309 G_CALLBACK (handle_get_initial_result_set), self);
310 g_signal_connect_swapped (self->skeleton, "handle-get-subsearch-result-set",
311 G_CALLBACK (handle_get_subsearch_result_set), self);
312 g_signal_connect_swapped (self->skeleton, "handle-get-result-metas",
313 G_CALLBACK (handle_get_result_metas), self);
314 g_signal_connect_swapped (self->skeleton, "handle-activate-result",
315 G_CALLBACK (handle_activate_result), self);
316 g_signal_connect_swapped (self->skeleton, "handle-launch-search",
317 G_CALLBACK (handle_launch_search), self);
318 }
319
320 gboolean
321 cc_search_provider_dbus_register (CcSearchProvider *self,
322 GDBusConnection *connection,
323 const gchar *object_path,
324 GError **error)
325 {
326 GDBusInterfaceSkeleton *skeleton;
327
328 skeleton = G_DBUS_INTERFACE_SKELETON (self->skeleton);
329
330 return g_dbus_interface_skeleton_export (skeleton, connection, object_path, error);
331 }
332
333 void
334 cc_search_provider_dbus_unregister (CcSearchProvider *self,
335 GDBusConnection *connection,
336 const gchar *object_path)
337 {
338 GDBusInterfaceSkeleton *skeleton;
339
340 skeleton = G_DBUS_INTERFACE_SKELETON (self->skeleton);
341
342 if (g_dbus_interface_skeleton_has_connection (skeleton, connection))
343 g_dbus_interface_skeleton_unexport_from_connection (skeleton, connection);
344 }
345
346 static void
347 cc_search_provider_dispose (GObject *object)
348 {
349 CcSearchProvider *self;
350
351 self = CC_SEARCH_PROVIDER (object);
352
353 g_clear_object (&self->skeleton);
354 g_clear_pointer (&self->iter_table, g_hash_table_destroy);
355
356 G_OBJECT_CLASS (cc_search_provider_parent_class)->dispose (object);
357 }
358
359 static void
360 cc_search_provider_class_init (CcSearchProviderClass *klass)
361 {
362 GObjectClass *object_class = G_OBJECT_CLASS (klass);
363
364 object_class->dispose = cc_search_provider_dispose;
365 }
366
367 CcSearchProvider *
368 cc_search_provider_new (void)
369 {
370 return g_object_new (CC_TYPE_SEARCH_PROVIDER, NULL);
371 }
372
373