gnome-shell-3.6.3.1/src/shell-app-system.c

No issues found

  1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
  2 
  3 #include "config.h"
  4 
  5 #include "shell-app-system.h"
  6 #include "shell-app-usage.h"
  7 #include <string.h>
  8 
  9 #include <gio/gio.h>
 10 #include <glib/gi18n.h>
 11 
 12 #include "shell-app-private.h"
 13 #include "shell-window-tracker-private.h"
 14 #include "shell-app-system-private.h"
 15 #include "shell-global.h"
 16 #include "shell-util.h"
 17 
 18 /* Vendor prefixes are something that can be preprended to a .desktop
 19  * file name.  Undo this.
 20  */
 21 static const char*const vendor_prefixes[] = { "gnome-",
 22                                               "fedora-",
 23                                               "mozilla-",
 24                                               "debian-",
 25                                               NULL };
 26 
 27 enum {
 28    PROP_0,
 29 
 30 };
 31 
 32 enum {
 33   APP_STATE_CHANGED,
 34   INSTALLED_CHANGED,
 35   LAST_SIGNAL
 36 };
 37 
 38 static guint signals[LAST_SIGNAL] = { 0 };
 39 
 40 struct _ShellAppSystemPrivate {
 41   GMenuTree *apps_tree;
 42 
 43   GHashTable *running_apps;
 44   GHashTable *visible_id_to_app;
 45   GHashTable *id_to_app;
 46 
 47   GSList *known_vendor_prefixes;
 48 
 49   GMenuTree *settings_tree;
 50   GHashTable *setting_id_to_app;
 51 };
 52 
 53 static void shell_app_system_finalize (GObject *object);
 54 static void on_apps_tree_changed_cb (GMenuTree *tree, gpointer user_data);
 55 static void on_settings_tree_changed_cb (GMenuTree *tree, gpointer user_data);
 56 
 57 G_DEFINE_TYPE(ShellAppSystem, shell_app_system, G_TYPE_OBJECT);
 58 
 59 static void shell_app_system_class_init(ShellAppSystemClass *klass)
 60 {
 61   GObjectClass *gobject_class = (GObjectClass *)klass;
 62 
 63   gobject_class->finalize = shell_app_system_finalize;
 64 
 65   signals[APP_STATE_CHANGED] = g_signal_new ("app-state-changed",
 66                                              SHELL_TYPE_APP_SYSTEM,
 67                                              G_SIGNAL_RUN_LAST,
 68                                              0,
 69                                              NULL, NULL, NULL,
 70                                              G_TYPE_NONE, 1,
 71                                              SHELL_TYPE_APP);
 72   signals[INSTALLED_CHANGED] =
 73     g_signal_new ("installed-changed",
 74 		  SHELL_TYPE_APP_SYSTEM,
 75 		  G_SIGNAL_RUN_LAST,
 76 		  G_STRUCT_OFFSET (ShellAppSystemClass, installed_changed),
 77           NULL, NULL, NULL,
 78 		  G_TYPE_NONE, 0);
 79 
 80   g_type_class_add_private (gobject_class, sizeof (ShellAppSystemPrivate));
 81 }
 82 
 83 static void
 84 shell_app_system_init (ShellAppSystem *self)
 85 {
 86   ShellAppSystemPrivate *priv;
 87 
 88   self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
 89                                                    SHELL_TYPE_APP_SYSTEM,
 90                                                    ShellAppSystemPrivate);
 91 
 92   priv->running_apps = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) g_object_unref, NULL);
 93   priv->id_to_app = g_hash_table_new_full (g_str_hash, g_str_equal,
 94                                            NULL,
 95                                            (GDestroyNotify)g_object_unref);
 96 
 97   /* All the objects in this hash table are owned by id_to_app */
 98   priv->visible_id_to_app = g_hash_table_new (g_str_hash, g_str_equal);
 99 
100   priv->setting_id_to_app = g_hash_table_new_full (g_str_hash, g_str_equal,
101                                                    NULL,
102                                                    (GDestroyNotify)g_object_unref);
103 
104   /* We want to track NoDisplay apps, so we add INCLUDE_NODISPLAY. We'll
105    * filter NoDisplay apps out when showing them to the user. */
106   priv->apps_tree = gmenu_tree_new ("applications.menu", GMENU_TREE_FLAGS_INCLUDE_NODISPLAY);
107   g_signal_connect (priv->apps_tree, "changed", G_CALLBACK (on_apps_tree_changed_cb), self);
108 
109   priv->settings_tree = gmenu_tree_new ("gnomecc.menu", 0);
110   g_signal_connect (priv->settings_tree, "changed", G_CALLBACK (on_settings_tree_changed_cb), self);
111 
112   on_apps_tree_changed_cb (priv->apps_tree, self);
113   on_settings_tree_changed_cb (priv->settings_tree, self);
114 }
115 
116 static void
117 shell_app_system_finalize (GObject *object)
118 {
119   ShellAppSystem *self = SHELL_APP_SYSTEM (object);
120   ShellAppSystemPrivate *priv = self->priv;
121 
122   g_object_unref (priv->apps_tree);
123   g_object_unref (priv->settings_tree);
124 
125   g_hash_table_destroy (priv->running_apps);
126   g_hash_table_destroy (priv->id_to_app);
127   g_hash_table_destroy (priv->visible_id_to_app);
128   g_hash_table_destroy (priv->setting_id_to_app);
129 
130   g_slist_free_full (priv->known_vendor_prefixes, g_free);
131   priv->known_vendor_prefixes = NULL;
132 
133   G_OBJECT_CLASS (shell_app_system_parent_class)->finalize (object);
134 }
135 
136 static char *
137 get_prefix_for_entry (GMenuTreeEntry *entry)
138 {
139   char *prefix = NULL, *file_prefix = NULL;
140   const char *id;
141   GFile *file;
142   char *name;
143   int i = 0;
144 
145   id = gmenu_tree_entry_get_desktop_file_id (entry);
146   file = g_file_new_for_path (gmenu_tree_entry_get_desktop_file_path (entry));
147   name = g_file_get_basename (file);
148 
149   if (!name)
150     {
151       g_object_unref (file);
152       return NULL;
153     }
154   for (i = 0; vendor_prefixes[i]; i++)
155     {
156       if (g_str_has_prefix (name, vendor_prefixes[i]))
157         {
158           file_prefix = g_strdup (vendor_prefixes[i]);
159           break;
160         }
161     }
162 
163   while (strcmp (name, id) != 0)
164     {
165       char *t;
166       char *pname;
167       GFile *parent = g_file_get_parent (file);
168 
169       if (!parent)
170         {
171           g_warn_if_reached ();
172           break;
173         }
174 
175       pname = g_file_get_basename (parent);
176       if (!pname)
177         {
178           g_object_unref (parent);
179           break;
180         }
181       if (!g_strstr_len (id, -1, pname))
182         {
183           /* handle <LegacyDir prefix="..."> */
184           char *t;
185           size_t name_len = strlen (name);
186           size_t id_len = strlen (id);
187           char *t_id = g_strdup (id);
188 
189           t_id[id_len - name_len] = '\0';
190           t = g_strdup(t_id);
191           g_free (prefix);
192           g_free (t_id);
193           g_free (name);
194           name = g_strdup (id);
195           prefix = t;
196 
197           g_object_unref (file);
198           file = parent;
199           g_free (pname);
200           g_free (file_prefix);
201           file_prefix = NULL;
202           break;
203         }
204 
205       t = g_strconcat (pname, "-", name, NULL);
206       g_free (name);
207       name = t;
208 
209       t = g_strconcat (pname, "-", prefix, NULL);
210       g_free (prefix);
211       prefix = t;
212 
213       g_object_unref (file);
214       file = parent;
215       g_free (pname);
216     }
217 
218   if (file)
219     g_object_unref (file);
220 
221   if (strcmp (name, id) == 0)
222     {
223       g_free (name);
224       if (file_prefix && !prefix)
225         return file_prefix;
226       if (file_prefix)
227         {
228           char *t = g_strconcat (prefix, "-", file_prefix, NULL);
229           g_free (prefix);
230           g_free (file_prefix);
231           prefix = t;
232         }
233       return prefix;
234     }
235 
236   g_free (name);
237   g_free (prefix);
238   g_free (file_prefix);
239   g_return_val_if_reached (NULL);
240 }
241 
242 static void
243 get_flattened_entries_recurse (GMenuTreeDirectory *dir,
244                                GHashTable         *entry_set)
245 {
246   GMenuTreeIter *iter = gmenu_tree_directory_iter (dir);
247   GMenuTreeItemType next_type;
248 
249   while ((next_type = gmenu_tree_iter_next (iter)) != GMENU_TREE_ITEM_INVALID)
250     {
251       gpointer item = NULL;
252 
253       switch (next_type)
254         {
255         case GMENU_TREE_ITEM_ENTRY:
256           {
257             GMenuTreeEntry *entry;
258             item = entry = gmenu_tree_iter_get_entry (iter);
259             /* Key is owned by entry */
260             g_hash_table_replace (entry_set,
261                                   (char*)gmenu_tree_entry_get_desktop_file_id (entry),
262                                   gmenu_tree_item_ref (entry));
263           }
264           break;
265         case GMENU_TREE_ITEM_DIRECTORY:
266           {
267             item = gmenu_tree_iter_get_directory (iter);
268             get_flattened_entries_recurse ((GMenuTreeDirectory*)item, entry_set);
269           }
270           break;
271         default:
272           break;
273         }
274       if (item != NULL)
275         gmenu_tree_item_unref (item);
276     }
277 
278   gmenu_tree_iter_unref (iter);
279 }
280 
281 static GHashTable *
282 get_flattened_entries_from_tree (GMenuTree *tree)
283 {
284   GHashTable *table;
285   GMenuTreeDirectory *root;
286 
287   table = g_hash_table_new_full (g_str_hash, g_str_equal,
288                                  (GDestroyNotify) NULL,
289                                  (GDestroyNotify) gmenu_tree_item_unref);
290 
291   root = gmenu_tree_get_root_directory (tree);
292   
293   if (root != NULL)
294     get_flattened_entries_recurse (root, table);
295 
296   gmenu_tree_item_unref (root);
297   
298   return table;
299 }
300 
301 static void
302 on_apps_tree_changed_cb (GMenuTree *tree,
303                          gpointer   user_data)
304 {
305   ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);
306   GError *error = NULL;
307   GHashTable *new_apps;
308   GHashTableIter iter;
309   gpointer key, value;
310   GSList *removed_apps = NULL;
311   GSList *removed_node;
312 
313   g_assert (tree == self->priv->apps_tree);
314 
315   g_hash_table_remove_all (self->priv->visible_id_to_app);
316   g_slist_free_full (self->priv->known_vendor_prefixes, g_free);
317   self->priv->known_vendor_prefixes = NULL;
318 
319   if (!gmenu_tree_load_sync (self->priv->apps_tree, &error))
320     {
321       if (error)
322         {
323           g_warning ("Failed to load apps: %s", error->message);
324           g_error_free (error);
325         }
326       else
327         {
328           g_warning ("Failed to load apps");
329         }
330       return;
331     }
332 
333   new_apps = get_flattened_entries_from_tree (self->priv->apps_tree);
334   g_hash_table_iter_init (&iter, new_apps);
335   while (g_hash_table_iter_next (&iter, &key, &value))
336     {
337       const char *id = key;
338       GMenuTreeEntry *entry = value;
339       GMenuTreeEntry *old_entry;
340       char *prefix;
341       ShellApp *app;
342       
343       prefix = get_prefix_for_entry (entry);
344       
345       if (prefix != NULL
346           && !g_slist_find_custom (self->priv->known_vendor_prefixes, prefix,
347                                    (GCompareFunc)g_strcmp0))
348         self->priv->known_vendor_prefixes = g_slist_append (self->priv->known_vendor_prefixes,
349                                                             prefix);
350       else
351         g_free (prefix);
352       
353       app = g_hash_table_lookup (self->priv->id_to_app, id);
354       if (app != NULL)
355         {
356           /* We hold a reference to the original entry temporarily,
357            * because otherwise the hash table would be referencing
358            * potentially free'd memory until we replace it below with
359            * the new data.
360            */
361           old_entry = shell_app_get_tree_entry (app);
362           gmenu_tree_item_ref (old_entry);
363           _shell_app_set_entry (app, entry);
364           g_object_ref (app);  /* Extra ref, removed in _replace below */
365         }
366       else
367         {
368           old_entry = NULL;
369           app = _shell_app_new (entry);
370         }
371       /* Note that "id" is owned by app->entry.  Since we're always
372        * setting a new entry, even if the app already exists in the
373        * hash table we need to replace the key so that the new id
374        * string is pointed to.
375        */
376       g_hash_table_replace (self->priv->id_to_app, (char*)id, app);
377       if (!gmenu_tree_entry_get_is_nodisplay_recurse (entry))
378         g_hash_table_replace (self->priv->visible_id_to_app, (char*)id, app);
379 
380       if (old_entry)
381         gmenu_tree_item_unref (old_entry);
382     }
383   /* Now iterate over the apps again; we need to unreference any apps
384    * which have been removed.  The JS code may still be holding a
385    * reference; that's fine.
386    */
387   g_hash_table_iter_init (&iter, self->priv->id_to_app);
388   while (g_hash_table_iter_next (&iter, &key, &value))
389     {
390       const char *id = key;
391       
392       if (!g_hash_table_lookup (new_apps, id))
393         removed_apps = g_slist_prepend (removed_apps, (char*)id);
394     }
395   for (removed_node = removed_apps; removed_node; removed_node = removed_node->next)
396     {
397       const char *id = removed_node->data;
398       g_hash_table_remove (self->priv->id_to_app, id);
399     }
400   g_slist_free (removed_apps);
401       
402   g_hash_table_destroy (new_apps);
403 
404   g_signal_emit (self, signals[INSTALLED_CHANGED], 0);
405 }
406 
407 static void
408 on_settings_tree_changed_cb (GMenuTree *tree,
409                              gpointer   user_data)
410 {
411   ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);
412   GError *error = NULL;
413   GHashTable *new_settings;
414   GHashTableIter iter;
415   gpointer key, value;
416 
417   g_assert (tree == self->priv->settings_tree);
418 
419   g_hash_table_remove_all (self->priv->setting_id_to_app);
420   if (!gmenu_tree_load_sync (self->priv->settings_tree, &error))
421     {
422       if (error)
423         {
424           g_warning ("Failed to load apps: %s", error->message);
425           g_error_free (error);
426         }
427       else
428         {
429           g_warning ("Failed to load apps");
430         }
431       return;
432     }
433 
434   new_settings = get_flattened_entries_from_tree (tree);
435 
436   g_hash_table_iter_init (&iter, new_settings);
437   while (g_hash_table_iter_next (&iter, &key, &value))
438     {
439       const char *id = key;
440       GMenuTreeEntry *entry = value;
441       ShellApp *app;
442 
443       app = _shell_app_new (entry);
444       g_hash_table_replace (self->priv->setting_id_to_app, (char*)id, app);
445     }
446   g_hash_table_destroy (new_settings);
447 }
448 
449 /**
450  * shell_app_system_get_tree:
451  *
452  * Return Value: (transfer none): The #GMenuTree for apps
453  */
454 GMenuTree *
455 shell_app_system_get_tree (ShellAppSystem *self)
456 {
457   return self->priv->apps_tree;
458 }
459 
460 /**
461  * shell_app_system_get_settings_tree:
462  *
463  * Return Value: (transfer none): The #GMenuTree for apps
464  */
465 GMenuTree *
466 shell_app_system_get_settings_tree (ShellAppSystem *self)
467 {
468   return self->priv->settings_tree;
469 }
470 
471 /**
472  * shell_app_system_lookup_setting:
473  * @system:
474  * @id: desktop file id
475  *
476  * Returns: (transfer none): Application in gnomecc.menu, or %NULL if none
477  */
478 ShellApp *
479 shell_app_system_lookup_setting (ShellAppSystem *self,
480                                  const char     *id)
481 {
482   ShellApp *app;
483 
484   /* Actually defer to the main app set if there's overlap */
485   app = shell_app_system_lookup_app (self, id);
486   if (app != NULL)
487     return app;
488 
489   return g_hash_table_lookup (self->priv->setting_id_to_app, id);
490 }
491 
492 /**
493  * shell_app_system_get_default:
494  *
495  * Return Value: (transfer none): The global #ShellAppSystem singleton
496  */
497 ShellAppSystem *
498 shell_app_system_get_default ()
499 {
500   static ShellAppSystem *instance = NULL;
501 
502   if (instance == NULL)
503     instance = g_object_new (SHELL_TYPE_APP_SYSTEM, NULL);
504 
505   return instance;
506 }
507 
508 /**
509  * shell_app_system_lookup_app:
510  *
511  * Find a #ShellApp corresponding to an id.
512  *
513  * Return value: (transfer none): The #ShellApp for id, or %NULL if none
514  */
515 ShellApp *
516 shell_app_system_lookup_app (ShellAppSystem   *self,
517                              const char       *id)
518 {
519   return g_hash_table_lookup (self->priv->id_to_app, id);
520 }
521 
522 /**
523  * shell_app_system_lookup_app_by_tree_entry:
524  * @system: a #ShellAppSystem
525  * @entry: a #GMenuTreeEntry
526  *
527  * Find a #ShellApp corresponding to a #GMenuTreeEntry.
528  *
529  * Return value: (transfer none): The #ShellApp for @entry, or %NULL if none
530  */
531 ShellApp *
532 shell_app_system_lookup_app_by_tree_entry (ShellAppSystem  *self,
533                                            GMenuTreeEntry  *entry)
534 {
535   /* If we looked up directly in ->entry_to_app, we'd lose the
536    * override of running apps.  Thus, indirect through the id.
537    */
538   return shell_app_system_lookup_app (self, gmenu_tree_entry_get_desktop_file_id (entry));
539 }
540 
541 /**
542  * shell_app_system_lookup_app_for_path:
543  * @system: a #ShellAppSystem
544  * @desktop_path: (type utf8): UTF-8 encoded absolute file name
545  *
546  * Find or create a #ShellApp corresponding to a given absolute file
547  * name which must be in the standard paths (XDG_DATA_DIRS).  For
548  * files outside the datadirs, this function returns %NULL.
549  *
550  * Return value: (transfer none): The #ShellApp for id, or %NULL if none
551  */
552 ShellApp *
553 shell_app_system_lookup_app_for_path (ShellAppSystem   *system,
554                                       const char       *desktop_path)
555 {
556   const char *basename;
557   const char *app_path;
558   ShellApp *app;
559 
560   basename = g_strrstr (desktop_path, "/");
561   if (basename)
562     basename += 1;
563   else
564     basename = desktop_path;
565 
566   app = shell_app_system_lookup_app (system, basename);
567   if (!app)
568     return NULL;
569 
570   app_path = gmenu_tree_entry_get_desktop_file_path (shell_app_get_tree_entry (app));
571   if (strcmp (desktop_path, app_path) != 0)
572     return NULL;
573 
574   return app;
575 }
576 
577 /**
578  * shell_app_system_lookup_heuristic_basename:
579  * @system: a #ShellAppSystem
580  * @id: Probable application identifier
581  *
582  * Find a valid application corresponding to a given
583  * heuristically determined application identifier
584  * string, or %NULL if none.
585  *
586  * Returns: (transfer none): A #ShellApp for @name
587  */
588 ShellApp *
589 shell_app_system_lookup_heuristic_basename (ShellAppSystem *system,
590                                             const char     *name)
591 {
592   ShellApp *result;
593   GSList *prefix;
594 
595   result = shell_app_system_lookup_app (system, name);
596   if (result != NULL)
597     return result;
598 
599   for (prefix = system->priv->known_vendor_prefixes; prefix; prefix = g_slist_next (prefix))
600     {
601       char *tmpid = g_strconcat ((char*)prefix->data, name, NULL);
602       result = shell_app_system_lookup_app (system, tmpid);
603       g_free (tmpid);
604       if (result != NULL)
605         return result;
606     }
607 
608   return NULL;
609 }
610 
611 /**
612  * shell_app_system_lookup_wmclass:
613  * @system: a #ShellAppSystem
614  * @wmclass: A WM_CLASS value
615  *
616  * Find a valid application corresponding to a WM_CLASS value.
617  *
618  * Returns: (transfer none): A #ShellApp for @wmclass
619  */
620 ShellApp *
621 shell_app_system_lookup_wmclass (ShellAppSystem *system,
622                                  const char     *wmclass)
623 {
624   char *canonicalized;
625   char *desktop_file;
626   ShellApp *app;
627 
628   if (wmclass == NULL)
629     return NULL;
630 
631   canonicalized = g_ascii_strdown (wmclass, -1);
632 
633   /* This handles "Fedora Eclipse", probably others.
634    * Note g_strdelimit is modify-in-place. */
635   g_strdelimit (canonicalized, " ", '-');
636 
637   desktop_file = g_strconcat (canonicalized, ".desktop", NULL);
638 
639   app = shell_app_system_lookup_heuristic_basename (system, desktop_file);
640 
641   g_free (canonicalized);
642   g_free (desktop_file);
643 
644   return app;
645 }
646 
647 void
648 _shell_app_system_notify_app_state_changed (ShellAppSystem *self,
649                                             ShellApp       *app)
650 {
651   ShellAppState state = shell_app_get_state (app);
652 
653   switch (state)
654     {
655     case SHELL_APP_STATE_RUNNING:
656       g_hash_table_insert (self->priv->running_apps, g_object_ref (app), NULL);
657       break;
658     case SHELL_APP_STATE_STARTING:
659       break;
660     case SHELL_APP_STATE_STOPPED:
661       g_hash_table_remove (self->priv->running_apps, app);
662       break;
663     }
664   g_signal_emit (self, signals[APP_STATE_CHANGED], 0, app);
665 }
666 
667 /**
668  * shell_app_system_get_running:
669  * @self: A #ShellAppSystem
670  *
671  * Returns the set of applications which currently have at least one
672  * open window in the given context.  The returned list will be sorted
673  * by shell_app_compare().
674  *
675  * Returns: (element-type ShellApp) (transfer container): Active applications
676  */
677 GSList *
678 shell_app_system_get_running (ShellAppSystem *self)
679 {
680   gpointer key, value;
681   GSList *ret;
682   GHashTableIter iter;
683 
684   g_hash_table_iter_init (&iter, self->priv->running_apps);
685 
686   ret = NULL;
687   while (g_hash_table_iter_next (&iter, &key, &value))
688     {
689       ShellApp *app = key;
690 
691       ret = g_slist_prepend (ret, app);
692     }
693 
694   ret = g_slist_sort (ret, (GCompareFunc)shell_app_compare);
695 
696   return ret;
697 }
698 
699 
700 static gint
701 compare_apps_by_usage (gconstpointer a,
702                        gconstpointer b,
703                        gpointer      data)
704 {
705   ShellAppUsage *usage = shell_app_usage_get_default ();
706 
707   ShellApp *app_a = (ShellApp*)a;
708   ShellApp *app_b = (ShellApp*)b;
709 
710   return shell_app_usage_compare (usage, "", app_a, app_b);
711 }
712 
713 static GSList *
714 sort_and_concat_results (ShellAppSystem *system,
715                          GSList         *prefix_matches,
716                          GSList         *substring_matches)
717 {
718   prefix_matches = g_slist_sort_with_data (prefix_matches,
719                                            compare_apps_by_usage,
720                                            system);
721   substring_matches = g_slist_sort_with_data (substring_matches,
722                                               compare_apps_by_usage,
723                                               system);
724   return g_slist_concat (prefix_matches, substring_matches);
725 }
726 
727 /**
728  * normalize_terms:
729  * @terms: (element-type utf8): Input search terms
730  *
731  * Returns: (element-type utf8) (transfer full): Unicode-normalized and lowercased terms
732  */
733 static GSList *
734 normalize_terms (GSList *terms)
735 {
736   GSList *normalized_terms = NULL;
737   GSList *iter;
738   for (iter = terms; iter; iter = iter->next)
739     {
740       const char *term = iter->data;
741       normalized_terms = g_slist_prepend (normalized_terms, shell_util_normalize_and_casefold (term));
742     }
743   return normalized_terms;
744 }
745 
746 static GSList *
747 search_tree (ShellAppSystem *self,
748              GSList         *terms,
749              GHashTable     *apps)
750 {
751   GSList *prefix_results = NULL;
752   GSList *substring_results = NULL;
753   GSList *normalized_terms;
754   GHashTableIter iter;
755   gpointer key, value;
756 
757   normalized_terms = normalize_terms (terms);
758 
759   g_hash_table_iter_init (&iter, apps);
760   while (g_hash_table_iter_next (&iter, &key, &value))
761     {
762       const char *id = key;
763       ShellApp *app = value;
764       (void)id;
765       _shell_app_do_match (app, normalized_terms,
766                            &prefix_results,
767                            &substring_results);
768     }
769   g_slist_free_full (normalized_terms, g_free);
770 
771   return sort_and_concat_results (self, prefix_results, substring_results);
772 
773 }
774 
775 /**
776  * shell_app_system_initial_search:
777  * @system: A #ShellAppSystem
778  * @terms: (element-type utf8): List of terms, logical AND
779  *
780  * Search through applications for the given search terms.
781  *
782  * Returns: (transfer container) (element-type ShellApp): List of applications
783  */
784 GSList *
785 shell_app_system_initial_search (ShellAppSystem  *self,
786                                  GSList          *terms)
787 {
788   return search_tree (self, terms, self->priv->visible_id_to_app);
789 }
790 
791 /**
792  * shell_app_system_subsearch:
793  * @system: A #ShellAppSystem
794  * @previous_results: (element-type ShellApp): List of previous results
795  * @terms: (element-type utf8): List of terms, logical AND
796  *
797  * Search through a previous result set; for more information, see
798  * js/ui/search.js.  Note the value of @prefs must be
799  * the same as passed to shell_app_system_initial_search().  Note that returned
800  * strings are only valid until a return to the main loop.
801  *
802  * Returns: (transfer container) (element-type ShellApp): List of application identifiers
803  */
804 GSList *
805 shell_app_system_subsearch (ShellAppSystem   *system,
806                             GSList           *previous_results,
807                             GSList           *terms)
808 {
809   GSList *iter;
810   GSList *prefix_results = NULL;
811   GSList *substring_results = NULL;
812   GSList *normalized_terms = normalize_terms (terms);
813 
814   for (iter = previous_results; iter; iter = iter->next)
815     {
816       ShellApp *app = iter->data;
817       
818       _shell_app_do_match (app, normalized_terms,
819                            &prefix_results,
820                            &substring_results);
821     }
822   g_slist_free_full (normalized_terms, g_free);
823 
824   /* Note that a shorter term might have matched as a prefix, but
825      when extended only as a substring, so we have to redo the
826      sort rather than reusing the existing ordering */
827   return sort_and_concat_results (system, prefix_results, substring_results);
828 }
829 
830 /**
831  * shell_app_system_search_settings:
832  * @system: A #ShellAppSystem
833  * @terms: (element-type utf8): List of terms, logical AND
834  *
835  * Search through settings for the given search terms.
836  *
837  * Returns: (transfer container) (element-type ShellApp): List of setting applications
838  */
839 GSList *
840 shell_app_system_search_settings (ShellAppSystem  *self,
841                                   GSList          *terms)
842 {
843   return search_tree (self, terms, self->priv->setting_id_to_app);
844 }