43
41
struct _ShellAppSystemPrivate {
44
42
GMenuTree *apps_tree;
44
GHashTable *entry_to_app;
46
GSList *known_vendor_prefixes;
45
48
GMenuTree *settings_tree;
47
GHashTable *app_id_to_info;
48
GHashTable *app_id_to_app;
50
GSList *cached_flattened_apps; /* ShellAppInfo */
51
GSList *cached_settings; /* ShellAppInfo */
52
GSList *known_vendor_prefixes;
56
guint app_change_timeout_id;
49
GHashTable *setting_entry_to_app;
59
static char *shell_app_info_get_prefix (ShellAppInfo *info);
60
52
static void shell_app_system_finalize (GObject *object);
61
static gboolean on_tree_changed (gpointer user_data);
62
static void on_tree_changed_cb (GMenuTree *tree, gpointer user_data);
63
static void reread_menus (ShellAppSystem *self);
53
static void on_apps_tree_changed_cb (GMenuTree *tree, gpointer user_data);
54
static void on_settings_tree_changed_cb (GMenuTree *tree, gpointer user_data);
65
56
G_DEFINE_TYPE(ShellAppSystem, shell_app_system, G_TYPE_OBJECT);
68
SHELL_APP_INFO_TYPE_ENTRY,
69
SHELL_APP_INFO_TYPE_DESKTOP_FILE,
70
SHELL_APP_INFO_TYPE_WINDOW
73
struct _ShellAppInfo {
74
ShellAppInfoType type;
76
/* We need this for two reasons. First, GKeyFile doesn't have a refcount.
77
* http://bugzilla.gnome.org/show_bug.cgi?id=590808
79
* But more generally we'll always need it so we know when to free this
80
* structure (short of weak references on each item).
84
char *casefolded_name;
85
char *name_collation_key;
86
char *casefolded_description;
87
char *casefolded_exec;
99
shell_app_info_ref (ShellAppInfo *info)
106
shell_app_info_unref (ShellAppInfo *info)
108
if (--info->refcount > 0)
111
g_free (info->casefolded_name);
112
g_free (info->name_collation_key);
113
g_free (info->casefolded_description);
117
case SHELL_APP_INFO_TYPE_ENTRY:
118
gmenu_tree_item_unref (info->entry);
120
case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
121
g_key_file_free (info->keyfile);
122
g_free (info->keyfile_path);
124
case SHELL_APP_INFO_TYPE_WINDOW:
125
g_object_unref (info->window);
126
g_free (info->window_id);
129
g_slice_free (ShellAppInfo, info);
132
static ShellAppInfo *
133
shell_app_info_new_from_tree_item (GMenuTreeItem *item)
140
info = g_slice_alloc0 (sizeof (ShellAppInfo));
141
info->type = SHELL_APP_INFO_TYPE_ENTRY;
143
info->entry = gmenu_tree_item_ref (item);
147
static ShellAppInfo *
148
shell_app_info_new_from_window (MetaWindow *window)
152
info = g_slice_alloc0 (sizeof (ShellAppInfo));
153
info->type = SHELL_APP_INFO_TYPE_WINDOW;
155
info->window = g_object_ref (window);
156
/* For windows, its id is simply its pointer address as a string.
157
* There are various other alternatives, but the address is unique
158
* and unchanging, which is pretty much the best we can do.
160
info->window_id = g_strdup_printf ("window:%p", window);
164
58
static void shell_app_system_class_init(ShellAppSystemClass *klass)
166
60
GObjectClass *gobject_class = (GObjectClass *)klass;
216
109
ShellAppSystem *self = SHELL_APP_SYSTEM (object);
217
110
ShellAppSystemPrivate *priv = self->priv;
219
gmenu_tree_remove_monitor (priv->apps_tree, on_tree_changed_cb, self);
220
gmenu_tree_remove_monitor (priv->settings_tree, on_tree_changed_cb, self);
222
gmenu_tree_unref (priv->apps_tree);
223
gmenu_tree_unref (priv->settings_tree);
225
g_hash_table_destroy (priv->app_id_to_info);
226
g_hash_table_destroy (priv->app_id_to_app);
228
g_slist_foreach (priv->cached_flattened_apps, (GFunc)shell_app_info_unref, NULL);
229
g_slist_free (priv->cached_flattened_apps);
230
priv->cached_flattened_apps = NULL;
112
g_object_unref (priv->apps_tree);
113
g_object_unref (priv->settings_tree);
115
g_hash_table_destroy (priv->entry_to_app);
116
g_hash_table_destroy (priv->setting_entry_to_app);
232
118
g_slist_foreach (priv->known_vendor_prefixes, (GFunc)g_free, NULL);
233
119
g_slist_free (priv->known_vendor_prefixes);
234
120
priv->known_vendor_prefixes = NULL;
236
g_slist_foreach (priv->cached_settings, (GFunc)shell_app_info_unref, NULL);
237
g_slist_free (priv->cached_settings);
238
priv->cached_settings = NULL;
240
G_OBJECT_CLASS (shell_app_system_parent_class)->finalize(object);
122
G_OBJECT_CLASS (shell_app_system_parent_class)->finalize (object);
244
gather_entries_recurse (ShellAppSystem *monitor,
247
GMenuTreeDirectory *root)
126
get_prefix_for_entry (GMenuTreeEntry *entry)
252
contents = gmenu_tree_directory_get_contents (root);
254
for (iter = contents; iter; iter = iter->next)
256
GMenuTreeItem *item = iter->data;
257
switch (gmenu_tree_item_get_type (item))
259
case GMENU_TREE_ITEM_ENTRY:
261
ShellAppInfo *app = shell_app_info_new_from_tree_item (item);
262
if (!g_hash_table_lookup (unique, shell_app_info_get_id (app)))
264
apps = g_slist_prepend (apps, app);
265
g_hash_table_insert (unique, (char*)shell_app_info_get_id (app), app);
269
case GMENU_TREE_ITEM_DIRECTORY:
271
GMenuTreeDirectory *dir = (GMenuTreeDirectory*)item;
272
apps = gather_entries_recurse (monitor, apps, unique, dir);
278
gmenu_tree_item_unref (item);
281
g_slist_free (contents);
128
char *prefix = NULL, *file_prefix = NULL;
134
id = gmenu_tree_entry_get_desktop_file_id (entry);
135
file = g_file_new_for_path (gmenu_tree_entry_get_desktop_file_path (entry));
136
name = g_file_get_basename (file);
140
g_object_unref (file);
143
for (i = 0; vendor_prefixes[i]; i++)
145
if (g_str_has_prefix (name, vendor_prefixes[i]))
147
file_prefix = g_strdup (vendor_prefixes[i]);
152
while (strcmp (name, id) != 0)
156
GFile *parent = g_file_get_parent (file);
160
g_warn_if_reached ();
164
pname = g_file_get_basename (parent);
167
g_object_unref (parent);
170
if (!g_strstr_len (id, -1, pname))
172
/* handle <LegacyDir prefix="..."> */
174
size_t name_len = strlen (name);
175
size_t id_len = strlen (id);
176
char *t_id = g_strdup (id);
178
t_id[id_len - name_len] = '\0';
183
name = g_strdup (id);
186
g_object_unref (file);
189
g_free (file_prefix);
194
t = g_strconcat (pname, "-", name, NULL);
198
t = g_strconcat (pname, "-", prefix, NULL);
202
g_object_unref (file);
208
g_object_unref (file);
210
if (strcmp (name, id) == 0)
213
if (file_prefix && !prefix)
217
char *t = g_strconcat (prefix, "-", file_prefix, NULL);
219
g_free (file_prefix);
227
g_free (file_prefix);
228
g_return_val_if_reached (NULL);
287
reread_entries (ShellAppSystem *self,
232
load_app_entry (ShellAppSystem *self,
233
GMenuTreeEntry *entry)
292
GMenuTreeDirectory *trunk;
294
trunk = gmenu_tree_get_root_directory (tree);
296
g_slist_foreach (*cache, (GFunc)shell_app_info_unref, NULL);
297
g_slist_free (*cache);
238
if (g_hash_table_lookup (self->priv->entry_to_app, entry))
241
prefix = get_prefix_for_entry (entry);
244
&& !g_slist_find_custom (self->priv->known_vendor_prefixes, prefix,
245
(GCompareFunc)g_strcmp0))
246
self->priv->known_vendor_prefixes = g_slist_append (self->priv->known_vendor_prefixes,
306
*cache = gather_entries_recurse (self, *cache, unique, trunk);
307
gmenu_tree_item_unref (trunk);
312
cache_by_id (ShellAppSystem *self, GSList *apps)
316
for (iter = apps; iter; iter = iter->next)
318
ShellAppInfo *info = iter->data;
319
const char *id = shell_app_info_get_id (info);
320
char *prefix = shell_app_info_get_prefix (info);
322
shell_app_info_ref (info);
323
/* the name is owned by the info itself */
326
&& !g_slist_find_custom (self->priv->known_vendor_prefixes, prefix,
327
(GCompareFunc)g_strcmp0))
328
self->priv->known_vendor_prefixes = g_slist_append (self->priv->known_vendor_prefixes,
332
g_hash_table_replace (self->priv->app_id_to_info, (char*)id, info);
337
reread_menus (ShellAppSystem *self)
339
GHashTable *unique = g_hash_table_new (g_str_hash, g_str_equal);
251
app = _shell_app_new (entry);
253
g_hash_table_insert (self->priv->entry_to_app, gmenu_tree_item_ref (entry), app);
257
gather_apps_recurse (ShellAppSystem *self,
258
GMenuTreeDirectory *root)
260
GMenuTreeIter *iter = gmenu_tree_directory_iter (root);
261
GMenuTreeItemType next_type;
263
while ((next_type = gmenu_tree_iter_next (iter)) != GMENU_TREE_ITEM_INVALID)
265
gpointer item = NULL;
269
case GMENU_TREE_ITEM_ENTRY:
271
item = gmenu_tree_iter_get_entry (iter);
272
load_app_entry (self, (GMenuTreeEntry*)item);
275
case GMENU_TREE_ITEM_DIRECTORY:
277
item = gmenu_tree_iter_get_directory (iter);
278
gather_apps_recurse (self, (GMenuTreeDirectory*)item);
285
gmenu_tree_item_unref (item);
288
gmenu_tree_iter_unref (iter);
292
gather_settings_recurse (ShellAppSystem *self,
293
GMenuTreeDirectory *root)
295
GMenuTreeIter *iter = gmenu_tree_directory_iter (root);
296
GMenuTreeItemType next_type;
298
while ((next_type = gmenu_tree_iter_next (iter)) != GMENU_TREE_ITEM_INVALID)
300
gpointer item = NULL;
304
case GMENU_TREE_ITEM_ENTRY:
308
item = gmenu_tree_iter_get_entry (iter);
309
if (g_hash_table_lookup (self->priv->setting_entry_to_app, item))
312
app = _shell_app_new (item);
314
g_hash_table_insert (self->priv->setting_entry_to_app, gmenu_tree_item_ref (item), app);
317
case GMENU_TREE_ITEM_DIRECTORY:
319
item = gmenu_tree_iter_get_directory (iter);
320
gather_settings_recurse (self, (GMenuTreeDirectory*)item);
327
gmenu_tree_item_unref (item);
330
gmenu_tree_iter_unref (iter);
334
on_apps_tree_changed_cb (GMenuTree *tree,
337
ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);
338
GError *error = NULL;
339
GMenuTreeDirectory *root;
341
g_assert (tree == self->priv->apps_tree);
343
g_hash_table_remove_all (self->priv->entry_to_app);
341
344
g_slist_foreach (self->priv->known_vendor_prefixes, (GFunc)g_free, NULL);
342
345
g_slist_free (self->priv->known_vendor_prefixes);
343
346
self->priv->known_vendor_prefixes = NULL;
345
reread_entries (self, &(self->priv->cached_flattened_apps), unique, self->priv->apps_tree);
346
g_hash_table_remove_all (unique);
347
reread_entries (self, &(self->priv->cached_settings), unique, self->priv->settings_tree);
348
g_hash_table_destroy (unique);
350
g_hash_table_remove_all (self->priv->app_id_to_info);
352
cache_by_id (self, self->priv->cached_flattened_apps);
353
cache_by_id (self, self->priv->cached_settings);
357
on_tree_changed (gpointer user_data)
359
ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);
348
if (!gmenu_tree_load_sync (self->priv->apps_tree, &error))
350
g_warning ("Failed to load apps: %s", error->message);
354
root = gmenu_tree_get_root_directory (self->priv->apps_tree);
358
gather_apps_recurse (self, root);
359
gmenu_tree_item_unref (root);
363
362
g_signal_emit (self, signals[INSTALLED_CHANGED], 0);
365
self->priv->app_change_timeout_id = 0;
370
on_tree_changed_cb (GMenuTree *monitor, gpointer user_data)
366
on_settings_tree_changed_cb (GMenuTree *tree,
372
369
ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);
374
/* GMenu currently gives us a separate notification on the entire
375
* menu tree for each node in the tree that might potentially have
376
* changed. (See http://bugzilla.gnome.org/show_bug.cgi?id=172046.)
377
* We need to compress these to avoid doing large extra amounts of
380
* Even when that bug is fixed, compression is still useful; for one
381
* thing we want to need to compress across notifications of changes
382
* to the settings tree. Second we want to compress if multiple
383
* changes are made to the desktop files at different times but in
387
if (self->priv->app_change_timeout_id != 0)
389
self->priv->app_change_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, 3000,
390
(GSourceFunc) on_tree_changed,
395
shell_app_info_get_type (void)
397
static GType gtype = G_TYPE_INVALID;
398
if (gtype == G_TYPE_INVALID)
400
gtype = g_boxed_type_register_static ("ShellAppInfo",
401
(GBoxedCopyFunc)shell_app_info_ref,
402
(GBoxedFreeFunc)shell_app_info_unref);
408
* shell_app_system_get_flattened_apps:
410
* Traverses a toplevel menu, and returns all items under it. Nested items
411
* are flattened. This value is computed on initial call and cached thereafter
412
* until the set of installed applications changes.
414
* Return value: (transfer none) (element-type ShellAppInfo): List of applications
417
shell_app_system_get_flattened_apps (ShellAppSystem *self)
419
return self->priv->cached_flattened_apps;
423
* shell_app_system_get_all_settings:
425
* Returns a list of application items under "settings.menu".
427
* Return value: (transfer none) (element-type ShellAppInfo): List of applications
430
shell_app_system_get_all_settings (ShellAppSystem *monitor)
432
return monitor->priv->cached_settings;
370
GError *error = NULL;
371
GMenuTreeDirectory *root;
373
g_assert (tree == self->priv->settings_tree);
375
g_hash_table_remove_all (self->priv->setting_entry_to_app);
376
if (!gmenu_tree_load_sync (self->priv->settings_tree, &error))
378
g_warning ("Failed to load settings: %s", error->message);
382
root = gmenu_tree_get_root_directory (self->priv->settings_tree);
386
gather_settings_recurse (self, root);
387
gmenu_tree_item_unref (root);
392
* shell_app_system_get_tree:
394
* Return Value: (transfer none): The #GMenuTree for apps
397
shell_app_system_get_tree (ShellAppSystem *self)
399
return self->priv->apps_tree;
403
* shell_app_system_get_settings_tree:
405
* Return Value: (transfer none): The #GMenuTree for apps
408
shell_app_system_get_settings_tree (ShellAppSystem *self)
410
return self->priv->settings_tree;
414
* shell_app_system_lookup_setting:
416
* @id: desktop file id
418
* Returns: (transfer none): Application in gnomecc.menu, or %NULL if none
421
shell_app_system_lookup_setting (ShellAppSystem *self,
424
GMenuTreeEntry *entry;
427
/* Actually defer to the main app set if there's overlap */
428
app = shell_app_system_lookup_app (self, id);
432
entry = gmenu_tree_get_entry_by_id (self->priv->settings_tree, id);
436
app = g_hash_table_lookup (self->priv->setting_entry_to_app, entry);
440
app = _shell_app_new (entry);
441
g_hash_table_insert (self->priv->setting_entry_to_app, gmenu_tree_item_ref (entry), app);
452
ShellAppSystem *appsys;
457
shell_app_system_on_app_weakref (gpointer data,
460
ShellAppRef *ref = data;
462
g_hash_table_remove (ref->appsys->priv->app_id_to_app, shell_app_info_get_id (ref->info));
463
shell_app_info_unref (ref->info);
468
* shell_app_system_get_app:
470
* Find or create a #ShellApp corresponding to an id; if already cached
471
* elsewhere in memory, return that instance. Otherwise, create a new
474
* Return value: (transfer full): The #ShellApp for id, or %NULL if none
463
* shell_app_system_lookup_app:
465
* Find a #ShellApp corresponding to an id.
467
* Return value: (transfer none): The #ShellApp for id, or %NULL if none
477
shell_app_system_get_app (ShellAppSystem *self,
470
shell_app_system_lookup_app (ShellAppSystem *self,
483
app = g_hash_table_lookup (self->priv->app_id_to_app, id);
485
return g_object_ref (app);
487
info = g_hash_table_lookup (self->priv->app_id_to_info, id);
473
GMenuTreeEntry *entry;
475
entry = gmenu_tree_get_entry_by_id (self->priv->apps_tree, id);
491
app = _shell_app_new (info);
497
* shell_app_system_get_app_for_path:
479
return g_hash_table_lookup (self->priv->entry_to_app, entry);
483
* shell_app_system_lookup_app_by_tree_entry:
484
* @system: a #ShellAppSystem
485
* @entry: a #GMenuTreeEntry
487
* Find a #ShellApp corresponding to a #GMenuTreeEntry.
489
* Return value: (transfer none): The #ShellApp for @entry, or %NULL if none
492
shell_app_system_lookup_app_by_tree_entry (ShellAppSystem *self,
493
GMenuTreeEntry *entry)
495
return g_hash_table_lookup (self->priv->entry_to_app, entry);
499
* shell_app_system_lookup_app_for_path:
498
500
* @system: a #ShellAppSystem
499
501
* @desktop_path: (type utf8): UTF-8 encoded absolute file name
501
* Find or create a #ShellApp corresponding to a given absolute
502
* file name which must be in the standard paths (XDG_DATA_DIRS).
503
* For files outside the datadirs, this function returns %NULL.
505
* If already cached elsewhere in memory, return that instance.
506
* Otherwise, create a new one.
508
* Return value: (transfer full): The #ShellApp for id, or %NULL if none
503
* Find or create a #ShellApp corresponding to a given absolute file
504
* name which must be in the standard paths (XDG_DATA_DIRS). For
505
* files outside the datadirs, this function returns %NULL.
507
* Return value: (transfer none): The #ShellApp for id, or %NULL if none
511
shell_app_system_get_app_for_path (ShellAppSystem *system,
512
const char *desktop_path)
510
shell_app_system_lookup_app_for_path (ShellAppSystem *system,
511
const char *desktop_path)
514
513
const char *basename;
514
const char *app_path;
517
517
basename = g_strrstr (desktop_path, "/");
635
MATCH_SUBSTRING, /* Not prefix, substring */
636
MATCH_MULTIPLE_SUBSTRING, /* Matches multiple criteria with substrings */
637
MATCH_PREFIX, /* Strict prefix */
638
MATCH_MULTIPLE_PREFIX, /* Matches multiple criteria, at least one prefix */
639
} ShellAppInfoSearchMatch;
642
normalize_and_casefold (const char *str)
569
* shell_app_system_get_all:
572
* Returns: (transfer container) (element-type ShellApp): All installed applications
575
shell_app_system_get_all (ShellAppSystem *self)
644
char *normalized, *result;
649
normalized = g_utf8_normalize (str, -1, G_NORMALIZE_ALL);
650
result = g_utf8_casefold (normalized, -1);
577
GSList *result = NULL;
581
g_hash_table_iter_init (&iter, self->priv->entry_to_app);
582
while (g_hash_table_iter_next (&iter, &key, &value))
584
ShellApp *app = value;
585
if (!g_desktop_app_info_get_nodisplay (shell_app_get_app_info (app)))
586
result = g_slist_prepend (result, app);
656
trim_exec_line (const char *str)
658
const char *start, *end, *pos;
660
end = strchr (str, ' ');
662
end = str + strlen (str);
665
while ((pos = strchr (start, '/')) && pos < end)
668
return g_strndup (start, end - start);
672
shell_app_info_init_search_data (ShellAppInfo *info)
677
char *normalized_exec;
679
g_assert (info->type == SHELL_APP_INFO_TYPE_ENTRY);
681
name = gmenu_tree_entry_get_name ((GMenuTreeEntry*)info->entry);
682
info->casefolded_name = normalize_and_casefold (name);
684
comment = gmenu_tree_entry_get_comment ((GMenuTreeEntry*)info->entry);
685
info->casefolded_description = normalize_and_casefold (comment);
687
exec = gmenu_tree_entry_get_exec ((GMenuTreeEntry*)info->entry);
688
normalized_exec = normalize_and_casefold (exec);
689
info->casefolded_exec = trim_exec_line (normalized_exec);
690
g_free (normalized_exec);
693
static ShellAppInfoSearchMatch
694
shell_app_info_match_terms (ShellAppInfo *info,
698
ShellAppInfoSearchMatch match;
700
if (G_UNLIKELY(!info->casefolded_name))
701
shell_app_info_init_search_data (info);
704
for (iter = terms; iter; iter = iter->next)
706
ShellAppInfoSearchMatch current_match;
707
const char *term = iter->data;
710
current_match = MATCH_NONE;
712
p = strstr (info->casefolded_name, term);
713
if (p == info->casefolded_name)
714
current_match = MATCH_PREFIX;
716
current_match = MATCH_SUBSTRING;
718
p = strstr (info->casefolded_exec, term);
721
if (p == info->casefolded_exec)
722
current_match = (current_match == MATCH_NONE) ? MATCH_PREFIX
723
: MATCH_MULTIPLE_PREFIX;
724
else if (current_match < MATCH_PREFIX)
725
current_match = (current_match == MATCH_NONE) ? MATCH_SUBSTRING
726
: MATCH_MULTIPLE_SUBSTRING;
729
if (info->casefolded_description && current_match < MATCH_PREFIX)
731
/* Only do substring matches, as prefix matches are not meaningful
732
* enough for descriptions
734
p = strstr (info->casefolded_description, term);
736
current_match = (current_match == MATCH_NONE) ? MATCH_SUBSTRING
737
: MATCH_MULTIPLE_SUBSTRING;
740
if (current_match == MATCH_NONE)
741
return current_match;
743
if (current_match > match)
744
match = current_match;
750
shell_app_info_compare (gconstpointer a,
592
compare_apps_by_name (gconstpointer a,
754
ShellAppSystem *system = data;
755
const char *id_a = a;
756
const char *id_b = b;
757
ShellAppInfo *info_a = g_hash_table_lookup (system->priv->app_id_to_info, id_a);
758
ShellAppInfo *info_b = g_hash_table_lookup (system->priv->app_id_to_info, id_b);
760
if (!info_a->name_collation_key)
761
info_a->name_collation_key = g_utf8_collate_key (gmenu_tree_entry_get_name ((GMenuTreeEntry*)info_a->entry), -1);
762
if (!info_b->name_collation_key)
763
info_b->name_collation_key = g_utf8_collate_key (gmenu_tree_entry_get_name ((GMenuTreeEntry*)info_b->entry), -1);
765
return strcmp (info_a->name_collation_key, info_b->name_collation_key);
596
ShellApp *app_a = (ShellApp*)a;
597
ShellApp *app_b = (ShellApp*)b;
599
return shell_app_compare_by_name (app_a, app_b);
801
635
for (iter = terms; iter; iter = iter->next)
803
637
const char *term = iter->data;
804
normalized_terms = g_slist_prepend (normalized_terms, normalize_and_casefold (term));
638
normalized_terms = g_slist_prepend (normalized_terms, shell_util_normalize_and_casefold (term));
806
640
return normalized_terms;
810
shell_app_system_do_match (ShellAppSystem *system,
813
GSList **multiple_prefix_results,
814
GSList **prefix_results,
815
GSList **multiple_substring_results,
816
GSList **substring_results)
818
const char *id = shell_app_info_get_id (info);
819
ShellAppInfoSearchMatch match;
821
if (shell_app_info_get_is_nodisplay (info))
824
match = shell_app_info_match_terms (info, terms);
829
case MATCH_MULTIPLE_PREFIX:
830
*multiple_prefix_results = g_slist_prepend (*multiple_prefix_results,
834
*prefix_results = g_slist_prepend (*prefix_results, (char *) id);
836
case MATCH_MULTIPLE_SUBSTRING:
837
*multiple_substring_results = g_slist_prepend (*multiple_substring_results,
840
case MATCH_SUBSTRING:
841
*substring_results = g_slist_prepend (*substring_results, (char *) id);
847
shell_app_system_initial_search_internal (ShellAppSystem *self,
644
search_tree (ShellAppSystem *self,
851
648
GSList *multiple_prefix_results = NULL;
852
649
GSList *prefix_results = NULL;
853
650
GSList *multiple_subtring_results = NULL;
854
651
GSList *substring_results = NULL;
856
GSList *normalized_terms = normalize_terms (terms);
858
for (iter = source; iter; iter = iter->next)
652
GSList *normalized_terms;
656
normalized_terms = normalize_terms (terms);
658
g_hash_table_iter_init (&iter, apps);
659
while (g_hash_table_iter_next (&iter, &key, &value))
860
ShellAppInfo *info = iter->data;
862
shell_app_system_do_match (self, info, normalized_terms,
863
&multiple_prefix_results, &prefix_results,
864
&multiple_subtring_results, &substring_results);
661
const char *id = key;
662
ShellApp *app = value;
664
_shell_app_do_match (app, normalized_terms,
665
&multiple_prefix_results, &prefix_results,
666
&multiple_subtring_results, &substring_results);
866
668
g_slist_foreach (normalized_terms, (GFunc)g_free, NULL);
867
669
g_slist_free (normalized_terms);
869
return sort_and_concat_results (self, multiple_prefix_results, prefix_results, multiple_subtring_results, substring_results);
671
return sort_and_concat_results (self, multiple_prefix_results, prefix_results,
672
multiple_subtring_results, substring_results);
873
677
* shell_app_system_initial_search:
874
678
* @system: A #ShellAppSystem
875
* @prefs: %TRUE if we should search preferences instead of apps
876
679
* @terms: (element-type utf8): List of terms, logical AND
878
* Search through applications for the given search terms. Note that returned
879
* strings are only valid until a return to the main loop.
681
* Search through applications for the given search terms.
881
* Returns: (transfer container) (element-type utf8): List of application identifiers
683
* Returns: (transfer container) (element-type ShellApp): List of applications
884
686
shell_app_system_initial_search (ShellAppSystem *self,
888
return shell_app_system_initial_search_internal (self, terms,
889
prefs ? self->priv->cached_settings : self->priv->cached_flattened_apps);
689
return search_tree (self, terms, self->priv->entry_to_app);
893
693
* shell_app_system_subsearch:
894
694
* @system: A #ShellAppSystem
895
* @prefs: %TRUE if we should search preferences instead of apps
896
* @previous_results: (element-type utf8): List of previous results
695
* @previous_results: (element-type ShellApp): List of previous results
897
696
* @terms: (element-type utf8): List of terms, logical AND
899
698
* Search through a previous result set; for more information, see
943
731
return sort_and_concat_results (system, multiple_prefix_results, prefix_results, multiple_substring_results, substring_results);
947
shell_app_info_get_id (ShellAppInfo *info)
951
case SHELL_APP_INFO_TYPE_ENTRY:
952
return gmenu_tree_entry_get_desktop_file_id ((GMenuTreeEntry*)info->entry);
953
case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
954
return info->keyfile_path;
955
case SHELL_APP_INFO_TYPE_WINDOW:
956
return info->window_id;
958
g_assert_not_reached ();
963
shell_app_info_get_prefix (ShellAppInfo *info)
965
char *prefix = NULL, *file_prefix = NULL;
971
if (info->type != SHELL_APP_INFO_TYPE_ENTRY)
974
id = gmenu_tree_entry_get_desktop_file_id ((GMenuTreeEntry*)info->entry);
975
file = g_file_new_for_path (gmenu_tree_entry_get_desktop_file_path ((GMenuTreeEntry*)info->entry));
976
name = g_file_get_basename (file);
980
g_object_unref (file);
983
for (i = 0; vendor_prefixes[i]; i++)
985
if (g_str_has_prefix (name, vendor_prefixes[i]))
987
file_prefix = g_strdup (vendor_prefixes[i]);
992
while (strcmp (name, id) != 0)
996
GFile *parent = g_file_get_parent (file);
1000
g_warn_if_reached ();
1004
pname = g_file_get_basename (parent);
1007
g_object_unref (parent);
1010
if (!g_strstr_len (id, -1, pname))
1012
/* handle <LegacyDir prefix="..."> */
1014
size_t name_len = strlen (name);
1015
size_t id_len = strlen (id);
1016
char *t_id = g_strdup (id);
1018
t_id[id_len - name_len] = '\0';
1023
name = g_strdup (id);
1026
g_object_unref (file);
1029
g_free (file_prefix);
1034
t = g_strconcat (pname, "-", name, NULL);
1038
t = g_strconcat (pname, "-", prefix, NULL);
1042
g_object_unref (file);
1048
g_object_unref (file);
1050
if (strcmp (name, id) == 0)
1053
if (file_prefix && !prefix)
1057
char *t = g_strconcat (prefix, "-", file_prefix, NULL);
1059
g_free (file_prefix);
1067
g_free (file_prefix);
1068
g_return_val_if_reached (NULL);
1071
#define DESKTOP_ENTRY_GROUP "Desktop Entry"
1074
shell_app_info_get_name (ShellAppInfo *info)
1078
case SHELL_APP_INFO_TYPE_ENTRY:
1079
return g_strdup (gmenu_tree_entry_get_name ((GMenuTreeEntry*)info->entry));
1080
case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
1081
return g_key_file_get_locale_string (info->keyfile, DESKTOP_ENTRY_GROUP, "Name", NULL, NULL);
1082
case SHELL_APP_INFO_TYPE_WINDOW:
1086
name = meta_window_get_wm_class (info->window);
1088
name = _("Unknown");
1089
return g_strdup (name);
1092
g_assert_not_reached ();
1097
shell_app_info_get_description (ShellAppInfo *info)
1101
case SHELL_APP_INFO_TYPE_ENTRY:
1102
return g_strdup (gmenu_tree_entry_get_comment ((GMenuTreeEntry*)info->entry));
1103
case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
1104
return g_key_file_get_locale_string (info->keyfile, DESKTOP_ENTRY_GROUP, "Comment", NULL, NULL);
1105
case SHELL_APP_INFO_TYPE_WINDOW:
1108
g_assert_not_reached ();
1113
shell_app_info_get_executable (ShellAppInfo *info)
1117
case SHELL_APP_INFO_TYPE_ENTRY:
1118
return g_strdup (gmenu_tree_entry_get_exec ((GMenuTreeEntry*)info->entry));
1119
case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
1120
return g_key_file_get_string (info->keyfile, DESKTOP_ENTRY_GROUP, "Exec", NULL);
1121
case SHELL_APP_INFO_TYPE_WINDOW:
1124
g_assert_not_reached ();
1129
shell_app_info_get_desktop_file_path (ShellAppInfo *info)
1133
case SHELL_APP_INFO_TYPE_ENTRY:
1134
return g_strdup (gmenu_tree_entry_get_desktop_file_path ((GMenuTreeEntry*)info->entry));
1135
case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
1136
return g_strdup (info->keyfile_path);
1137
case SHELL_APP_INFO_TYPE_WINDOW:
1140
g_assert_not_reached ();
1145
themed_icon_from_name (const char *iconname)
1152
if (g_path_is_absolute (iconname))
1155
file = g_file_new_for_path (iconname);
1156
icon = G_ICON (g_file_icon_new (file));
1157
g_object_unref (file);
1162
tmp_name = strdup (iconname);
1163
/* Work around a common mistake in desktop files */
1164
if ((p = strrchr (tmp_name, '.')) != NULL &&
1165
(strcmp (p, ".png") == 0 ||
1166
strcmp (p, ".xpm") == 0 ||
1167
strcmp (p, ".svg") == 0))
1171
icon = g_themed_icon_new (tmp_name);
1179
* shell_app_info_get_icon:
1180
* @info: A #ShellAppInfo
1182
* Get the #GIcon associated with this app; for apps "faked" from a #MetaWindow,
1185
* Returns: (transfer full): The icon for @info, or %NULL
1188
shell_app_info_get_icon (ShellAppInfo *info)
1190
char *iconname = NULL;
1193
/* This code adapted from gdesktopappinfo.c
1194
* Copyright (C) 2006-2007 Red Hat, Inc.
1195
* Copyright © 2007 Ryan Lortie
1201
case SHELL_APP_INFO_TYPE_ENTRY:
1202
return themed_icon_from_name (gmenu_tree_entry_get_icon ((GMenuTreeEntry*)info->entry));
1203
case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
1204
iconname = g_key_file_get_locale_string (info->keyfile, DESKTOP_ENTRY_GROUP, "Icon", NULL, NULL);
1205
icon = themed_icon_from_name (iconname);
1209
case SHELL_APP_INFO_TYPE_WINDOW:
1212
g_assert_not_reached ();
1217
* shell_app_system_get_sections:
1219
* return names of sections in applications menu.
1221
* Returns: (element-type utf8) (transfer full): List of Names
1224
shell_app_system_get_sections (ShellAppSystem *system)
1227
GSList *i, *contents;
1228
GMenuTreeDirectory *root;
1230
root = gmenu_tree_get_root_directory (system->priv->apps_tree);
1232
if (G_UNLIKELY (!root))
1233
g_error ("applications.menu not found.");
1235
contents = gmenu_tree_directory_get_contents (root);
1237
for (i = contents; i; i = i->next)
1239
GMenuTreeItem *item = i->data;
1240
if (gmenu_tree_item_get_type (item) == GMENU_TREE_ITEM_DIRECTORY)
1242
char *name = g_strdup (gmenu_tree_directory_get_name ((GMenuTreeDirectory*)item));
1246
res = g_list_append (res, name);
1248
gmenu_tree_item_unref (item);
1251
g_slist_free (contents);
1257
* shell_app_info_get_section:
1259
* return name of section, that contain this application.
1260
* Returns: (transfer full): section name
1263
shell_app_info_get_section (ShellAppInfo *info)
1266
GMenuTreeDirectory *dir, *parent;
1268
if (info->type != SHELL_APP_INFO_TYPE_ENTRY)
1271
dir = gmenu_tree_item_get_parent ((GMenuTreeItem*)info->entry);
1275
parent = gmenu_tree_item_get_parent ((GMenuTreeItem*)dir);
1281
GMenuTreeDirectory *pparent = gmenu_tree_item_get_parent ((GMenuTreeItem*)parent);
1284
gmenu_tree_item_unref ((GMenuTreeItem*)dir);
1289
name = g_strdup (gmenu_tree_directory_get_name (dir));
1291
gmenu_tree_item_unref ((GMenuTreeItem*)dir);
1292
gmenu_tree_item_unref ((GMenuTreeItem*)parent);
1297
shell_app_info_get_is_nodisplay (ShellAppInfo *info)
1301
case SHELL_APP_INFO_TYPE_ENTRY:
1302
return gmenu_tree_entry_get_is_nodisplay ((GMenuTreeEntry*)info->entry);
1303
case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
1304
case SHELL_APP_INFO_TYPE_WINDOW:
1307
g_assert_not_reached ();
1312
* shell_app_info_is_transient:
1314
* A "transient" application is one which represents
1315
* just an open window, i.e. we don't know how to launch it
1319
shell_app_info_is_transient (ShellAppInfo *info)
1321
return info->type == SHELL_APP_INFO_TYPE_WINDOW;
1325
* shell_app_info_create_icon_texture:
1327
* Look up the icon for this application, and create a #ClutterTexture
1328
* for it at the given size.
1330
* Return value: (transfer none): A floating #ClutterActor
1333
shell_app_info_create_icon_texture (ShellAppInfo *info, float size)
1340
if (info->type == SHELL_APP_INFO_TYPE_WINDOW)
1342
ret = st_texture_cache_bind_pixbuf_property (st_texture_cache_get_default (),
1343
G_OBJECT (info->window),
1348
icon = shell_app_info_get_icon (info);
1351
ret = st_texture_cache_load_gicon (st_texture_cache_get_default (), NULL, icon, (int)size);
1352
g_object_unref (icon);
1358
icon = g_themed_icon_new ("application-x-executable");
1359
ret = st_texture_cache_load_gicon (st_texture_cache_get_default (), NULL, icon, (int)size);
1360
g_object_unref (icon);
1367
* shell_app_info_get_source_window:
1368
* @info: A #ShellAppInfo
1370
* Returns: (transfer none): If @info is tracking a #MetaWindow,
1371
* return that window. Otherwise, return %NULL.
1374
shell_app_info_get_source_window (ShellAppInfo *info)
1376
if (info->type == SHELL_APP_INFO_TYPE_WINDOW)
1377
return info->window;
1382
_gather_pid_callback (GDesktopAppInfo *gapp,
1387
ShellWindowTracker *tracker;
1389
g_return_if_fail (data != NULL);
1391
app = SHELL_APP (data);
1392
tracker = shell_window_tracker_get_default ();
1394
_shell_window_tracker_add_child_process_app (tracker,
1400
* shell_app_info_launch_full:
1401
* @timestamp: Event timestamp, or 0 for current event timestamp
1402
* @uris: List of uris to pass to application
1403
* @workspace: Start on this workspace, or -1 for default
1404
* @startup_id: (out): Returned startup notification ID, or %NULL if none
1408
shell_app_info_launch_full (ShellAppInfo *info,
1415
ShellApp *shell_app;
1416
GDesktopAppInfo *gapp;
1417
GdkAppLaunchContext *context;
1419
ShellGlobal *global;
1425
if (info->type == SHELL_APP_INFO_TYPE_WINDOW)
1427
/* We can't pass URIs into a window; shouldn't hit this
1428
* code path. If we do, fix the caller to disallow it.
1430
g_return_val_if_fail (uris == NULL, TRUE);
1432
meta_window_activate (info->window, timestamp);
1435
else if (info->type == SHELL_APP_INFO_TYPE_ENTRY)
1437
/* Can't use g_desktop_app_info_new, see bug 614879 */
1438
const char *filename = gmenu_tree_entry_get_desktop_file_path ((GMenuTreeEntry *)info->entry);
1439
gapp = g_desktop_app_info_new_from_filename (filename);
1443
char *filename = shell_app_info_get_desktop_file_path (info);
1444
gapp = g_desktop_app_info_new_from_filename (filename);
1450
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Not found");
1454
global = shell_global_get ();
1455
screen = shell_global_get_screen (global);
1458
timestamp = clutter_get_current_event_time ();
1461
workspace = meta_screen_get_active_workspace_index (screen);
1463
context = gdk_app_launch_context_new ();
1464
gdk_app_launch_context_set_timestamp (context, timestamp);
1465
gdk_app_launch_context_set_desktop (context, workspace);
1467
shell_app = shell_app_system_get_app (shell_app_system_get_default (),
1468
shell_app_info_get_id (info));
1470
/* In the case where we know an app, we handle reaping the child internally,
1471
* in the window tracker.
1473
if (shell_app != NULL)
1474
ret = g_desktop_app_info_launch_uris_as_manager (gapp, uris,
1475
G_APP_LAUNCH_CONTEXT (context),
1476
G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
1478
_gather_pid_callback, shell_app,
1481
ret = g_desktop_app_info_launch_uris_as_manager (gapp, uris,
1482
G_APP_LAUNCH_CONTEXT (context),
1483
G_SPAWN_SEARCH_PATH,
1488
g_object_unref (G_OBJECT (gapp));
1494
shell_app_info_launch (ShellAppInfo *info,
1497
return shell_app_info_launch_full (info, 0, NULL, -1, NULL, error);
735
* shell_app_system_search_settings:
736
* @system: A #ShellAppSystem
737
* @terms: (element-type utf8): List of terms, logical AND
739
* Search through settings for the given search terms.
741
* Returns: (transfer container) (element-type ShellApp): List of setting applications
744
shell_app_system_search_settings (ShellAppSystem *self,
747
return search_tree (self, terms, self->priv->setting_entry_to_app);