1 /* -*- mode:c; c-file-style: "gnu"; indent-tabs-mode: nil -*-
2 * Copyright (C) 2003, 2004, 2011 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <config.h>
19
20 #include "gmenu-tree.h"
21
22 #include <string.h>
23 #include <errno.h>
24 #include <stdlib.h>
25
26 #include "menu-layout.h"
27 #include "menu-monitor.h"
28 #include "menu-util.h"
29
30 /* private */
31 typedef struct GMenuTreeItem GMenuTreeItem;
32 #define GMENU_TREE_ITEM(i) ((GMenuTreeItem *)(i))
33 #define GMENU_TREE_DIRECTORY(i) ((GMenuTreeDirectory *)(i))
34 #define GMENU_TREE_ENTRY(i) ((GMenuTreeEntry *)(i))
35 #define GMENU_TREE_SEPARATOR(i) ((GMenuTreeSeparator *)(i))
36 #define GMENU_TREE_HEADER(i) ((GMenuTreeHeader *)(i))
37 #define GMENU_TREE_ALIAS(i) ((GMenuTreeAlias *)(i))
38
39 enum {
40 PROP_0,
41
42 PROP_MENU_BASENAME,
43 PROP_MENU_PATH,
44 PROP_FLAGS
45 };
46
47 /* Signals */
48 enum
49 {
50 CHANGED,
51 LAST_SIGNAL
52 };
53
54 static guint gmenu_tree_signals [LAST_SIGNAL] = { 0 };
55
56 struct _GMenuTree
57 {
58 GObject parent_instance;
59
60 char *basename;
61 char *non_prefixed_basename;
62 char *path;
63 char *canonical_path;
64
65 GMenuTreeFlags flags;
66
67 GSList *menu_file_monitors;
68
69 MenuLayoutNode *layout;
70 GMenuTreeDirectory *root;
71 GHashTable *entries_by_id;
72
73 guint canonical : 1;
74 guint loaded : 1;
75 };
76
77 G_DEFINE_TYPE (GMenuTree, gmenu_tree, G_TYPE_OBJECT)
78
79 struct GMenuTreeItem
80 {
81 volatile gint refcount;
82
83 GMenuTreeItemType type;
84
85 GMenuTreeDirectory *parent;
86 GMenuTree *tree;
87 };
88
89 struct GMenuTreeIter
90 {
91 volatile gint refcount;
92
93 GMenuTreeItem *item;
94 GSList *contents;
95 GSList *contents_iter;
96 };
97
98 struct GMenuTreeDirectory
99 {
100 GMenuTreeItem item;
101
102 DesktopEntry *directory_entry;
103 char *name;
104
105 GSList *entries;
106 GSList *subdirs;
107
108 MenuLayoutValues default_layout_values;
109 GSList *default_layout_info;
110 GSList *layout_info;
111 GSList *contents;
112
113 guint only_unallocated : 1;
114 guint is_nodisplay : 1;
115 guint layout_pending_separator : 1;
116 guint preprocessed : 1;
117
118 /* 16 bits should be more than enough; G_MAXUINT16 means no inline header */
119 guint will_inline_header : 16;
120 };
121
122 struct GMenuTreeEntry
123 {
124 GMenuTreeItem item;
125
126 DesktopEntry *desktop_entry;
127 char *desktop_file_id;
128
129 guint is_excluded : 1;
130 guint is_unallocated : 1;
131 };
132
133 struct GMenuTreeSeparator
134 {
135 GMenuTreeItem item;
136 };
137
138 struct GMenuTreeHeader
139 {
140 GMenuTreeItem item;
141
142 GMenuTreeDirectory *directory;
143 };
144
145 struct GMenuTreeAlias
146 {
147 GMenuTreeItem item;
148
149 GMenuTreeDirectory *directory;
150 GMenuTreeItem *aliased_item;
151 };
152
153 static gboolean gmenu_tree_load_layout (GMenuTree *tree,
154 GError **error);
155 static void gmenu_tree_force_reload (GMenuTree *tree);
156 static gboolean gmenu_tree_build_from_layout (GMenuTree *tree,
157 GError **error);
158 static void gmenu_tree_force_rebuild (GMenuTree *tree);
159 static void gmenu_tree_resolve_files (GMenuTree *tree,
160 GHashTable *loaded_menu_files,
161 MenuLayoutNode *layout);
162 static void gmenu_tree_force_recanonicalize (GMenuTree *tree);
163 static void gmenu_tree_invoke_monitors (GMenuTree *tree);
164
165 static void gmenu_tree_item_unref_and_unset_parent (gpointer itemp);
166
167 typedef enum
168 {
169 MENU_FILE_MONITOR_INVALID = 0,
170 MENU_FILE_MONITOR_FILE,
171 MENU_FILE_MONITOR_NONEXISTENT_FILE,
172 MENU_FILE_MONITOR_DIRECTORY
173 } MenuFileMonitorType;
174
175 typedef struct
176 {
177 MenuFileMonitorType type;
178 MenuMonitor *monitor;
179 } MenuFileMonitor;
180
181 static void
handle_nonexistent_menu_file_changed(MenuMonitor * monitor,MenuMonitorEvent event,const char * path,GMenuTree * tree)182 handle_nonexistent_menu_file_changed (MenuMonitor *monitor,
183 MenuMonitorEvent event,
184 const char *path,
185 GMenuTree *tree)
186 {
187 if (event == MENU_MONITOR_EVENT_CHANGED ||
188 event == MENU_MONITOR_EVENT_CREATED)
189 {
190 menu_verbose ("\"%s\" %s, marking tree for recanonicalization\n",
191 path,
192 event == MENU_MONITOR_EVENT_CREATED ? "created" : "changed");
193
194 gmenu_tree_force_recanonicalize (tree);
195 gmenu_tree_invoke_monitors (tree);
196 }
197 }
198
199 static void
handle_menu_file_changed(MenuMonitor * monitor,MenuMonitorEvent event,const char * path,GMenuTree * tree)200 handle_menu_file_changed (MenuMonitor *monitor,
201 MenuMonitorEvent event,
202 const char *path,
203 GMenuTree *tree)
204 {
205 menu_verbose ("\"%s\" %s, marking tree for recanicalization\n",
206 path,
207 event == MENU_MONITOR_EVENT_CREATED ? "created" :
208 event == MENU_MONITOR_EVENT_CHANGED ? "changed" : "deleted");
209
210 gmenu_tree_force_recanonicalize (tree);
211 gmenu_tree_invoke_monitors (tree);
212 }
213
214 static void
handle_menu_file_directory_changed(MenuMonitor * monitor,MenuMonitorEvent event,const char * path,GMenuTree * tree)215 handle_menu_file_directory_changed (MenuMonitor *monitor,
216 MenuMonitorEvent event,
217 const char *path,
218 GMenuTree *tree)
219 {
220 if (!g_str_has_suffix (path, ".menu"))
221 return;
222
223 menu_verbose ("\"%s\" %s, marking tree for recanicalization\n",
224 path,
225 event == MENU_MONITOR_EVENT_CREATED ? "created" :
226 event == MENU_MONITOR_EVENT_CHANGED ? "changed" : "deleted");
227
228 gmenu_tree_force_recanonicalize (tree);
229 gmenu_tree_invoke_monitors (tree);
230 }
231
232 static void
gmenu_tree_add_menu_file_monitor(GMenuTree * tree,const char * path,MenuFileMonitorType type)233 gmenu_tree_add_menu_file_monitor (GMenuTree *tree,
234 const char *path,
235 MenuFileMonitorType type)
236 {
237 MenuFileMonitor *monitor;
238
239 monitor = g_slice_new0 (MenuFileMonitor);
240
241 monitor->type = type;
242
243 switch (type)
244 {
245 case MENU_FILE_MONITOR_FILE:
246 menu_verbose ("Adding a menu file monitor for \"%s\"\n", path);
247
248 monitor->monitor = menu_get_file_monitor (path);
249 menu_monitor_add_notify (monitor->monitor,
250 (MenuMonitorNotifyFunc) handle_menu_file_changed,
251 tree);
252 break;
253
254 case MENU_FILE_MONITOR_NONEXISTENT_FILE:
255 menu_verbose ("Adding a menu file monitor for non-existent \"%s\"\n", path);
256
257 monitor->monitor = menu_get_file_monitor (path);
258 menu_monitor_add_notify (monitor->monitor,
259 (MenuMonitorNotifyFunc) handle_nonexistent_menu_file_changed,
260 tree);
261 break;
262
263 case MENU_FILE_MONITOR_DIRECTORY:
264 menu_verbose ("Adding a menu directory monitor for \"%s\"\n", path);
265
266 monitor->monitor = menu_get_directory_monitor (path);
267 menu_monitor_add_notify (monitor->monitor,
268 (MenuMonitorNotifyFunc) handle_menu_file_directory_changed,
269 tree);
270 break;
271
272 default:
273 g_assert_not_reached ();
274 break;
275 }
276
277 tree->menu_file_monitors = g_slist_prepend (tree->menu_file_monitors, monitor);
278 }
279
280 static void
remove_menu_file_monitor(MenuFileMonitor * monitor,GMenuTree * tree)281 remove_menu_file_monitor (MenuFileMonitor *monitor,
282 GMenuTree *tree)
283 {
284 switch (monitor->type)
285 {
286 case MENU_FILE_MONITOR_FILE:
287 menu_monitor_remove_notify (monitor->monitor,
288 (MenuMonitorNotifyFunc) handle_menu_file_changed,
289 tree);
290 break;
291
292 case MENU_FILE_MONITOR_NONEXISTENT_FILE:
293 menu_monitor_remove_notify (monitor->monitor,
294 (MenuMonitorNotifyFunc) handle_nonexistent_menu_file_changed,
295 tree);
296 break;
297
298 case MENU_FILE_MONITOR_DIRECTORY:
299 menu_monitor_remove_notify (monitor->monitor,
300 (MenuMonitorNotifyFunc) handle_menu_file_directory_changed,
301 tree);
302 break;
303
304 default:
305 g_assert_not_reached ();
306 break;
307 }
308
309 menu_monitor_unref (monitor->monitor);
310 monitor->monitor = NULL;
311
312 monitor->type = MENU_FILE_MONITOR_INVALID;
313
314 g_slice_free (MenuFileMonitor, monitor);
315 }
316
317 static void
gmenu_tree_remove_menu_file_monitors(GMenuTree * tree)318 gmenu_tree_remove_menu_file_monitors (GMenuTree *tree)
319 {
320 menu_verbose ("Removing all menu file monitors\n");
321
322 g_slist_foreach (tree->menu_file_monitors,
323 (GFunc) remove_menu_file_monitor,
324 tree);
325 g_slist_free (tree->menu_file_monitors);
326 tree->menu_file_monitors = NULL;
327 }
328
329 static gboolean
canonicalize_path(GMenuTree * tree,const char * path)330 canonicalize_path (GMenuTree *tree,
331 const char *path)
332 {
333 tree->canonical_path = realpath (path, NULL);
334 if (tree->canonical_path)
335 {
336 tree->canonical = TRUE;
337 gmenu_tree_add_menu_file_monitor (tree,
338 tree->canonical_path,
339 MENU_FILE_MONITOR_FILE);
340 }
341 else
342 {
343 gmenu_tree_add_menu_file_monitor (tree,
344 path,
345 MENU_FILE_MONITOR_NONEXISTENT_FILE);
346 }
347
348 return tree->canonical;
349 }
350
351 static gboolean
canonicalize_basename_with_config_dir(GMenuTree * tree,const char * basename,const char * config_dir)352 canonicalize_basename_with_config_dir (GMenuTree *tree,
353 const char *basename,
354 const char *config_dir)
355 {
356 gboolean ret;
357 char *path;
358
359 path = g_build_filename (config_dir, "menus", basename, NULL);
360 ret = canonicalize_path (tree, path);
361 g_free (path);
362
363 return ret;
364 }
365
366 static void
canonicalize_basename(GMenuTree * tree,const char * basename)367 canonicalize_basename (GMenuTree *tree,
368 const char *basename)
369 {
370 if (!canonicalize_basename_with_config_dir (tree,
371 basename,
372 g_get_user_config_dir ()))
373 {
374 const char * const *system_config_dirs;
375 int i;
376
377 system_config_dirs = g_get_system_config_dirs ();
378
379 i = 0;
380 while (system_config_dirs[i] != NULL)
381 {
382 if (canonicalize_basename_with_config_dir (tree,
383 basename,
384 system_config_dirs[i]))
385 break;
386
387 ++i;
388 }
389 }
390 }
391
392 static gboolean
gmenu_tree_canonicalize_path(GMenuTree * tree,GError ** error)393 gmenu_tree_canonicalize_path (GMenuTree *tree,
394 GError **error)
395 {
396 const char *menu_file = NULL;
397
398 if (tree->canonical)
399 return TRUE;
400
401 g_assert (tree->canonical_path == NULL);
402
403 gmenu_tree_remove_menu_file_monitors (tree);
404
405 if (tree->path)
406 {
407 menu_file = tree->path;
408 canonicalize_path (tree, tree->path);
409 }
410 else
411 {
412 const gchar *xdg_menu_prefix;
413
414 menu_file = tree->basename;
415 xdg_menu_prefix = g_getenv ("XDG_MENU_PREFIX");
416
417 if (xdg_menu_prefix != NULL)
418 {
419 gchar *prefixed_basename;
420
421 prefixed_basename = g_strdup_printf ("%sapplications.menu",
422 xdg_menu_prefix);
423
424 /* Some gnome-menus using applications just use "applications.menu"
425 * as the basename and expect gnome-menus to prefix it. Others (e.g.
426 * Alacarte) explicitly use "${XDG_MENU_PREFIX}applications.menu" as
427 * the basename, because they want to save changes to the right files
428 * in ~. In both cases, we want to use "applications-merged" as the
429 * merge directory (as required by the fd.o menu spec), so we save
430 * the non-prefixed basename and use it later when calling
431 * menu_layout_load().
432 */
433 if (!g_strcmp0 (tree->basename, "applications.menu") ||
434 !g_strcmp0 (tree->basename, prefixed_basename))
435 {
436 canonicalize_basename (tree, prefixed_basename);
437 g_free (tree->non_prefixed_basename);
438 tree->non_prefixed_basename = g_strdup ("applications.menu");
439 }
440 g_free (prefixed_basename);
441 }
442
443 if (!tree->canonical)
444 canonicalize_basename (tree, tree->basename);
445 }
446
447 if (tree->canonical)
448 {
449 menu_verbose ("Successfully looked up menu_file for \"%s\": %s\n",
450 menu_file, tree->canonical_path);
451 return TRUE;
452 }
453 else
454 {
455 g_set_error (error,
456 G_IO_ERROR,
457 G_IO_ERROR_FAILED,
458 "Failed to look up menu_file for \"%s\"\n",
459 menu_file);
460 return FALSE;
461 }
462 }
463
464 static void
gmenu_tree_force_recanonicalize(GMenuTree * tree)465 gmenu_tree_force_recanonicalize (GMenuTree *tree)
466 {
467 gmenu_tree_remove_menu_file_monitors (tree);
468
469 if (tree->canonical)
470 {
471 gmenu_tree_force_reload (tree);
472
473 g_free (tree->canonical_path);
474 tree->canonical_path = NULL;
475
476 tree->canonical = FALSE;
477 }
478 }
479
480 /**
481 * gmenu_tree_new:
482 * @menu_basename: Basename of menu file
483 * @flags: Flags controlling menu content
484 *
485 * Returns: (transfer full): A new #GMenuTree instance
486 */
487 GMenuTree *
gmenu_tree_new(const char * menu_basename,GMenuTreeFlags flags)488 gmenu_tree_new (const char *menu_basename,
489 GMenuTreeFlags flags)
490 {
491 g_return_val_if_fail (menu_basename != NULL, NULL);
492
493 return g_object_new (GMENU_TYPE_TREE,
494 "menu-basename", menu_basename,
495 "flags", flags,
496 NULL);
497 }
498
499 /**
500 * gmenu_tree_new_fo_path:
501 * @menu_path: Path of menu file
502 * @flags: Flags controlling menu content
503 *
504 * Returns: (transfer full): A new #GMenuTree instance
505 */
506 GMenuTree *
gmenu_tree_new_for_path(const char * menu_path,GMenuTreeFlags flags)507 gmenu_tree_new_for_path (const char *menu_path,
508 GMenuTreeFlags flags)
509 {
510 g_return_val_if_fail (menu_path != NULL, NULL);
511
512 return g_object_new (GMENU_TYPE_TREE,
513 "menu-path", menu_path,
514 "flags", flags,
515 NULL);
516 }
517
518 static GObject *
gmenu_tree_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_properties)519 gmenu_tree_constructor (GType type,
520 guint n_construct_properties,
521 GObjectConstructParam *construct_properties)
522 {
523 GObject *obj;
524 GMenuTree *self;
525
526 obj = G_OBJECT_CLASS (gmenu_tree_parent_class)->constructor (type,
527 n_construct_properties,
528 construct_properties);
529
530 /* If GMenuTree:menu-path is set, then we should make sure that
531 * GMenuTree:menu-basename is unset (especially as it has a default
532 * value). This has to be done here, in the constructor, since the
533 * properties are construct-only. */
534
535 self = GMENU_TREE (obj);
536
537 if (self->path != NULL)
538 g_object_set (self, "menu-basename", NULL, NULL);
539
540 return obj;
541 }
542
543 static void
gmenu_tree_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)544 gmenu_tree_set_property (GObject *object,
545 guint prop_id,
546 const GValue *value,
547 GParamSpec *pspec)
548 {
549 GMenuTree *self = GMENU_TREE (object);
550
551 switch (prop_id)
552 {
553 case PROP_MENU_BASENAME:
554 self->basename = g_value_dup_string (value);
555 break;
556
557 case PROP_MENU_PATH:
558 self->path = g_value_dup_string (value);
559 break;
560
561 case PROP_FLAGS:
562 self->flags = g_value_get_flags (value);
563 break;
564
565 default:
566 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
567 break;
568 }
569 }
570
571 static void
gmenu_tree_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)572 gmenu_tree_get_property (GObject *object,
573 guint prop_id,
574 GValue *value,
575 GParamSpec *pspec)
576 {
577 GMenuTree *self = GMENU_TREE (object);
578
579 switch (prop_id)
580 {
581 case PROP_MENU_BASENAME:
582 g_value_set_string (value, self->basename);
583 break;
584 case PROP_MENU_PATH:
585 g_value_set_string (value, self->path);
586 break;
587 case PROP_FLAGS:
588 g_value_set_flags (value, self->flags);
589 break;
590 default:
591 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
592 break;
593 }
594 }
595
596 static void
gmenu_tree_finalize(GObject * object)597 gmenu_tree_finalize (GObject *object)
598 {
599 GMenuTree *tree = GMENU_TREE (object);
600
601 gmenu_tree_force_recanonicalize (tree);
602
603 if (tree->basename != NULL)
604 g_free (tree->basename);
605 tree->basename = NULL;
606
607 g_free (tree->non_prefixed_basename);
608 tree->non_prefixed_basename = NULL;
609
610 if (tree->path != NULL)
611 g_free (tree->path);
612 tree->path = NULL;
613
614 if (tree->canonical_path != NULL)
615 g_free (tree->canonical_path);
616 tree->canonical_path = NULL;
617
618 g_hash_table_destroy (tree->entries_by_id);
619 tree->entries_by_id = NULL;
620
621 G_OBJECT_CLASS (gmenu_tree_parent_class)->finalize (object);
622 }
623
624 static void
gmenu_tree_init(GMenuTree * self)625 gmenu_tree_init (GMenuTree *self)
626 {
627 self->entries_by_id = g_hash_table_new (g_str_hash, g_str_equal);
628 }
629
630 static void
gmenu_tree_class_init(GMenuTreeClass * klass)631 gmenu_tree_class_init (GMenuTreeClass *klass)
632 {
633 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
634
635 gobject_class->constructor = gmenu_tree_constructor;
636 gobject_class->get_property = gmenu_tree_get_property;
637 gobject_class->set_property = gmenu_tree_set_property;
638 gobject_class->finalize = gmenu_tree_finalize;
639
640 /**
641 * GMenuTree:menu-basename:
642 *
643 * The name of the menu file; must be a basename or a relative path. The file
644 * will be looked up in $XDG_CONFIG_DIRS/menus/. See the Desktop Menu
645 * specification.
646 */
647 g_object_class_install_property (gobject_class,
648 PROP_MENU_BASENAME,
649 g_param_spec_string ("menu-basename", "", "",
650 "applications.menu",
651 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
652 /**
653 * GMenuTree:menu-path:
654 *
655 * The full path of the menu file. If set, GMenuTree:menu-basename will get
656 * ignored.
657 */
658 g_object_class_install_property (gobject_class,
659 PROP_MENU_PATH,
660 g_param_spec_string ("menu-path", "", "",
661 NULL,
662 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
663 /**
664 * GMenuTree:flags:
665 *
666 * Flags controlling the content of the menu.
667 */
668 g_object_class_install_property (gobject_class,
669 PROP_FLAGS,
670 g_param_spec_flags ("flags", "", "",
671 GMENU_TYPE_TREE_FLAGS,
672 GMENU_TREE_FLAGS_NONE,
673 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
674
675 /**
676 * GMenuTree:changed:
677 *
678 * This signal is emitted when applications are added, removed, or
679 * upgraded. But note the new data will only be visible after
680 * gmenu_tree_load_sync() or a variant thereof is invoked.
681 */
682 gmenu_tree_signals[CHANGED] =
683 g_signal_new ("changed",
684 G_TYPE_FROM_CLASS (klass),
685 G_SIGNAL_RUN_LAST,
686 0,
687 NULL, NULL,
688 g_cclosure_marshal_VOID__VOID,
689 G_TYPE_NONE, 0);
690 }
691
692 /**
693 * gmenu_tree_get_canonical_menu_path:
694 * @tree: a #GMenuTree
695 *
696 * This function is only available if the tree has been loaded via
697 * gmenu_tree_load_sync() or a variant thereof.
698 *
699 * Returns: The absolute and canonicalized path to the loaded menu file
700 */
701 const char *
gmenu_tree_get_canonical_menu_path(GMenuTree * tree)702 gmenu_tree_get_canonical_menu_path (GMenuTree *tree)
703 {
704 g_return_val_if_fail (GMENU_IS_TREE (tree), NULL);
705 g_return_val_if_fail (tree->loaded, NULL);
706
707 return tree->canonical_path;
708 }
709
710 /**
711 * gmenu_tree_load_sync:
712 * @tree: a #GMenuTree
713 * @error: a #GError
714 *
715 * Synchronously load the menu contents. This function
716 * performs a significant amount of blocking I/O if the
717 * tree has not been loaded yet.
718 *
719 * Returns: %TRUE on success, %FALSE on error
720 */
721 gboolean
gmenu_tree_load_sync(GMenuTree * tree,GError ** error)722 gmenu_tree_load_sync (GMenuTree *tree,
723 GError **error)
724 {
725 GError *local_error = NULL;
726
727 if (tree->loaded)
728 return TRUE;
729
730 if (!gmenu_tree_build_from_layout (tree, &local_error))
731 {
732 if (local_error)
733 g_propagate_error (error, local_error);
734 return FALSE;
735 }
736
737 tree->loaded = TRUE;
738
739 return TRUE;
740 }
741
742 /**
743 * gmenu_tree_get_root_directory:
744 * @tree: a #GMenuTree
745 *
746 * Get the root directory; you must have loaded the tree first (at
747 * least once) via gmenu_tree_load_sync() or a variant thereof.
748 *
749 * Returns: (transfer full): Root of the tree
750 */
751 GMenuTreeDirectory *
gmenu_tree_get_root_directory(GMenuTree * tree)752 gmenu_tree_get_root_directory (GMenuTree *tree)
753 {
754 g_return_val_if_fail (tree != NULL, NULL);
755 g_return_val_if_fail (tree->loaded, NULL);
756
757 return gmenu_tree_item_ref (tree->root);
758 }
759
760 static GMenuTreeDirectory *
find_path(GMenuTreeDirectory * directory,const char * path)761 find_path (GMenuTreeDirectory *directory,
762 const char *path)
763 {
764 const char *name;
765 char *slash;
766 char *freeme;
767 GSList *tmp;
768
769 while (path[0] == G_DIR_SEPARATOR) path++;
770
771 if (path[0] == '\0')
772 return directory;
773
774 freeme = NULL;
775 slash = strchr (path, G_DIR_SEPARATOR);
776 if (slash)
777 {
778 name = freeme = g_strndup (path, slash - path);
779 path = slash + 1;
780 }
781 else
782 {
783 name = path;
784 path = NULL;
785 }
786
787 tmp = directory->contents;
788 while (tmp != NULL)
789 {
790 GMenuTreeItem *item = tmp->data;
791
792 if (item->type != GMENU_TREE_ITEM_DIRECTORY)
793 {
794 tmp = tmp->next;
795 continue;
796 }
797
798 if (!strcmp (name, GMENU_TREE_DIRECTORY (item)->name))
799 {
800 g_free (freeme);
801
802 if (path)
803 return find_path (GMENU_TREE_DIRECTORY (item), path);
804 else
805 return GMENU_TREE_DIRECTORY (item);
806 }
807
808 tmp = tmp->next;
809 }
810
811 g_free (freeme);
812
813 return NULL;
814 }
815
816 GMenuTreeDirectory *
gmenu_tree_get_directory_from_path(GMenuTree * tree,const char * path)817 gmenu_tree_get_directory_from_path (GMenuTree *tree,
818 const char *path)
819 {
820 GMenuTreeDirectory *root;
821 GMenuTreeDirectory *directory;
822
823 g_return_val_if_fail (tree != NULL, NULL);
824 g_return_val_if_fail (path != NULL, NULL);
825
826 if (path[0] != G_DIR_SEPARATOR)
827 return NULL;
828
829 if (!(root = gmenu_tree_get_root_directory (tree)))
830 return NULL;
831
832 directory = find_path (root, path);
833
834 gmenu_tree_item_unref (root);
835
836 return directory ? gmenu_tree_item_ref (directory) : NULL;
837 }
838
839 /**
840 * gmenu_tree_get_entry_by_id:
841 * @tree: a #GMenuTree
842 * @id: a desktop file ID
843 *
844 * Look up the entry corresponding to the given "desktop file id".
845 *
846 * Returns: (transfer full): A newly referenced #GMenuTreeEntry, or %NULL if none
847 */
848 GMenuTreeEntry *
gmenu_tree_get_entry_by_id(GMenuTree * tree,const char * id)849 gmenu_tree_get_entry_by_id (GMenuTree *tree,
850 const char *id)
851 {
852 GMenuTreeEntry *entry;
853
854 g_return_val_if_fail (tree->loaded, NULL);
855
856 entry = g_hash_table_lookup (tree->entries_by_id, id);
857 if (entry != NULL)
858 gmenu_tree_item_ref (entry);
859
860 return entry;
861 }
862
863 static void
gmenu_tree_invoke_monitors(GMenuTree * tree)864 gmenu_tree_invoke_monitors (GMenuTree *tree)
865 {
866 g_signal_emit (tree, gmenu_tree_signals[CHANGED], 0);
867 }
868
869 static GMenuTreeDirectory *
get_parent(GMenuTreeItem * item)870 get_parent (GMenuTreeItem *item)
871 {
872 g_return_val_if_fail (item != NULL, NULL);
873 return item->parent ? gmenu_tree_item_ref (item->parent) : NULL;
874 }
875
876 /**
877 * gmenu_tree_directory_get_parent:
878 * @directory: a #GMenuTreeDirectory
879 *
880 * Returns: (transfer full): The parent directory, or %NULL if none
881 */
882 GMenuTreeDirectory *
gmenu_tree_directory_get_parent(GMenuTreeDirectory * directory)883 gmenu_tree_directory_get_parent (GMenuTreeDirectory *directory)
884 {
885 return get_parent ((GMenuTreeItem *)directory);
886 }
887
888 /**
889 * gmenu_tree_entry_get_parent:
890 * @entry: a #GMenuTreeEntry
891 *
892 * Returns: (transfer full): The parent directory, or %NULL if none
893 */
894 GMenuTreeDirectory *
gmenu_tree_entry_get_parent(GMenuTreeEntry * entry)895 gmenu_tree_entry_get_parent (GMenuTreeEntry *entry)
896 {
897 return get_parent ((GMenuTreeItem *)entry);
898 }
899
900 /**
901 * gmenu_tree_alias_get_parent:
902 * @alias: a #GMenuTreeAlias
903 *
904 * Returns: (transfer full): The parent directory, or %NULL if none
905 */
906 GMenuTreeDirectory *
gmenu_tree_alias_get_parent(GMenuTreeAlias * alias)907 gmenu_tree_alias_get_parent (GMenuTreeAlias *alias)
908 {
909 return get_parent ((GMenuTreeItem *)alias);
910 }
911
912 /**
913 * gmenu_tree_header_get_parent:
914 * @header: a #GMenuTreeHeader
915 *
916 * Returns: (transfer full): The parent directory, or %NULL if none
917 */
918 GMenuTreeDirectory *
gmenu_tree_header_get_parent(GMenuTreeHeader * header)919 gmenu_tree_header_get_parent (GMenuTreeHeader *header)
920 {
921 return get_parent ((GMenuTreeItem *)header);
922 }
923
924 /**
925 * gmenu_tree_separator_get_parent:
926 * @separator: a #GMenuTreeSeparator
927 *
928 * Returns: (transfer full): The parent directory, or %NULL if none
929 */
930 GMenuTreeDirectory *
gmenu_tree_separator_get_parent(GMenuTreeSeparator * separator)931 gmenu_tree_separator_get_parent (GMenuTreeSeparator *separator)
932 {
933 return get_parent ((GMenuTreeItem *)separator);
934 }
935
936 static void
gmenu_tree_item_set_parent(GMenuTreeItem * item,GMenuTreeDirectory * parent)937 gmenu_tree_item_set_parent (GMenuTreeItem *item,
938 GMenuTreeDirectory *parent)
939 {
940 g_return_if_fail (item != NULL);
941
942 item->parent = parent;
943 }
944
945 /**
946 * gmenu_tree_iter_ref: (skip)
947 * @iter: iter
948 *
949 * Increment the reference count of @iter
950 */
951 GMenuTreeIter *
gmenu_tree_iter_ref(GMenuTreeIter * iter)952 gmenu_tree_iter_ref (GMenuTreeIter *iter)
953 {
954 g_atomic_int_inc (&iter->refcount);
955 return iter;
956 }
957
958 /**
959 * gmenu_tree_iter_unref: (skip)
960 * @iter: iter
961 *
962 * Decrement the reference count of @iter
963 */
964 void
gmenu_tree_iter_unref(GMenuTreeIter * iter)965 gmenu_tree_iter_unref (GMenuTreeIter *iter)
966 {
967 if (!g_atomic_int_dec_and_test (&iter->refcount))
968 return;
969
970 g_slist_foreach (iter->contents, (GFunc)gmenu_tree_item_unref, NULL);
971 g_slist_free (iter->contents);
972
973 g_slice_free (GMenuTreeIter, iter);
974 }
975
976 /**
977 * gmenu_tree_directory_iter:
978 * @directory: directory
979 *
980 * Returns: (transfer full): A new iterator over the directory contents
981 */
982 GMenuTreeIter *
gmenu_tree_directory_iter(GMenuTreeDirectory * directory)983 gmenu_tree_directory_iter (GMenuTreeDirectory *directory)
984 {
985 GMenuTreeIter *iter;
986
987 g_return_val_if_fail (directory != NULL, NULL);
988
989 iter = g_slice_new0 (GMenuTreeIter);
990 iter->refcount = 1;
991
992 iter->contents = g_slist_copy (directory->contents);
993 iter->contents_iter = iter->contents;
994 g_slist_foreach (iter->contents, (GFunc) gmenu_tree_item_ref, NULL);
995
996 return iter;
997 }
998
999 /**
1000 * gmenu_tree_iter_next:
1001 * @iter: iter
1002 *
1003 * Change the iterator to the next item, and return its type. If
1004 * there are no more items, %GMENU_TREE_ITEM_INVALID is returned.
1005 *
1006 * Returns: The type of the next item that can be retrived from the iterator
1007 */
1008 GMenuTreeItemType
gmenu_tree_iter_next(GMenuTreeIter * iter)1009 gmenu_tree_iter_next (GMenuTreeIter *iter)
1010 {
1011 g_return_val_if_fail (iter != NULL, GMENU_TREE_ITEM_INVALID);
1012
1013 if (iter->contents_iter)
1014 {
1015 iter->item = iter->contents_iter->data;
1016 iter->contents_iter = iter->contents_iter->next;
1017 return iter->item->type;
1018 }
1019 else
1020 return GMENU_TREE_ITEM_INVALID;
1021 }
1022
1023 /**
1024 * gmenu_tree_iter_get_directory:
1025 * @iter: iter
1026 *
1027 * This method may only be called if gmenu_tree_iter_next()
1028 * returned GMENU_TREE_ITEM_DIRECTORY.
1029 *
1030 * Returns: (transfer full): A directory
1031 */
1032 GMenuTreeDirectory *
gmenu_tree_iter_get_directory(GMenuTreeIter * iter)1033 gmenu_tree_iter_get_directory (GMenuTreeIter *iter)
1034 {
1035 g_return_val_if_fail (iter != NULL, NULL);
1036 g_return_val_if_fail (iter->item != NULL, NULL);
1037 g_return_val_if_fail (iter->item->type == GMENU_TREE_ITEM_DIRECTORY, NULL);
1038
1039 return (GMenuTreeDirectory*)gmenu_tree_item_ref (iter->item);
1040 }
1041
1042 /**
1043 * gmenu_tree_iter_get_entry:
1044 * @iter: iter
1045 *
1046 * This method may only be called if gmenu_tree_iter_next()
1047 * returned GMENU_TREE_ITEM_ENTRY.
1048 *
1049 * Returns: (transfer full): An entry
1050 */
1051 GMenuTreeEntry *
gmenu_tree_iter_get_entry(GMenuTreeIter * iter)1052 gmenu_tree_iter_get_entry (GMenuTreeIter *iter)
1053 {
1054 g_return_val_if_fail (iter != NULL, NULL);
1055 g_return_val_if_fail (iter->item != NULL, NULL);
1056 g_return_val_if_fail (iter->item->type == GMENU_TREE_ITEM_ENTRY, NULL);
1057
1058 return (GMenuTreeEntry*)gmenu_tree_item_ref (iter->item);
1059 }
1060
1061 /**
1062 * gmenu_tree_iter_get_header:
1063 * @iter: iter
1064 *
1065 * This method may only be called if gmenu_tree_iter_next()
1066 * returned GMENU_TREE_ITEM_HEADER.
1067 *
1068 * Returns: (transfer full): A header
1069 */
1070 GMenuTreeHeader *
gmenu_tree_iter_get_header(GMenuTreeIter * iter)1071 gmenu_tree_iter_get_header (GMenuTreeIter *iter)
1072 {
1073 g_return_val_if_fail (iter != NULL, NULL);
1074 g_return_val_if_fail (iter->item != NULL, NULL);
1075 g_return_val_if_fail (iter->item->type == GMENU_TREE_ITEM_HEADER, NULL);
1076
1077 return (GMenuTreeHeader*)gmenu_tree_item_ref (iter->item);
1078 }
1079
1080 /**
1081 * gmenu_tree_iter_get_alias:
1082 * @iter: iter
1083 *
1084 * This method may only be called if gmenu_tree_iter_next()
1085 * returned GMENU_TREE_ITEM_ALIAS.
1086 *
1087 * Returns: (transfer full): An alias
1088 */
1089 GMenuTreeAlias *
gmenu_tree_iter_get_alias(GMenuTreeIter * iter)1090 gmenu_tree_iter_get_alias (GMenuTreeIter *iter)
1091 {
1092 g_return_val_if_fail (iter != NULL, NULL);
1093 g_return_val_if_fail (iter->item != NULL, NULL);
1094 g_return_val_if_fail (iter->item->type == GMENU_TREE_ITEM_ALIAS, NULL);
1095
1096 return (GMenuTreeAlias*)gmenu_tree_item_ref (iter->item);
1097 }
1098
1099 /**
1100 * gmenu_tree_iter_get_separator:
1101 * @iter: iter
1102 *
1103 * This method may only be called if gmenu_tree_iter_next()
1104 * returned #GMENU_TREE_ITEM_SEPARATOR.
1105 *
1106 * Returns: (transfer full): A separator
1107 */
1108 GMenuTreeSeparator *
gmenu_tree_iter_get_separator(GMenuTreeIter * iter)1109 gmenu_tree_iter_get_separator (GMenuTreeIter *iter)
1110 {
1111 g_return_val_if_fail (iter != NULL, NULL);
1112 g_return_val_if_fail (iter->item != NULL, NULL);
1113 g_return_val_if_fail (iter->item->type == GMENU_TREE_ITEM_SEPARATOR, NULL);
1114
1115 return (GMenuTreeSeparator*)gmenu_tree_item_ref (iter->item);
1116 }
1117
1118 const char *
gmenu_tree_directory_get_name(GMenuTreeDirectory * directory)1119 gmenu_tree_directory_get_name (GMenuTreeDirectory *directory)
1120 {
1121 g_return_val_if_fail (directory != NULL, NULL);
1122
1123 if (!directory->directory_entry)
1124 return directory->name;
1125
1126 return desktop_entry_get_name (directory->directory_entry);
1127 }
1128
1129 const char *
gmenu_tree_directory_get_generic_name(GMenuTreeDirectory * directory)1130 gmenu_tree_directory_get_generic_name (GMenuTreeDirectory *directory)
1131 {
1132 g_return_val_if_fail (directory != NULL, NULL);
1133
1134 if (!directory->directory_entry)
1135 return NULL;
1136
1137 return desktop_entry_get_generic_name (directory->directory_entry);
1138 }
1139
1140 const char *
gmenu_tree_directory_get_comment(GMenuTreeDirectory * directory)1141 gmenu_tree_directory_get_comment (GMenuTreeDirectory *directory)
1142 {
1143 g_return_val_if_fail (directory != NULL, NULL);
1144
1145 if (!directory->directory_entry)
1146 return NULL;
1147
1148 return desktop_entry_get_comment (directory->directory_entry);
1149 }
1150
1151 /**
1152 * gmenu_tree_directory_get_icon:
1153 * @directory: a #GMenuTreeDirectory
1154 *
1155 * Gets the icon for the directory.
1156 *
1157 * Returns: (transfer none): The #GIcon for this directory
1158 */
1159 GIcon *
gmenu_tree_directory_get_icon(GMenuTreeDirectory * directory)1160 gmenu_tree_directory_get_icon (GMenuTreeDirectory *directory)
1161 {
1162 g_return_val_if_fail (directory != NULL, NULL);
1163
1164 if (!directory->directory_entry)
1165 return NULL;
1166
1167 return desktop_entry_get_icon (directory->directory_entry);
1168 }
1169
1170 const char *
gmenu_tree_directory_get_desktop_file_path(GMenuTreeDirectory * directory)1171 gmenu_tree_directory_get_desktop_file_path (GMenuTreeDirectory *directory)
1172 {
1173 g_return_val_if_fail (directory != NULL, NULL);
1174
1175 if (!directory->directory_entry)
1176 return NULL;
1177
1178 return desktop_entry_get_path (directory->directory_entry);
1179 }
1180
1181 const char *
gmenu_tree_directory_get_menu_id(GMenuTreeDirectory * directory)1182 gmenu_tree_directory_get_menu_id (GMenuTreeDirectory *directory)
1183 {
1184 g_return_val_if_fail (directory != NULL, NULL);
1185
1186 return directory->name;
1187 }
1188
1189 gboolean
gmenu_tree_directory_get_is_nodisplay(GMenuTreeDirectory * directory)1190 gmenu_tree_directory_get_is_nodisplay (GMenuTreeDirectory *directory)
1191 {
1192 g_return_val_if_fail (directory != NULL, FALSE);
1193
1194 return directory->is_nodisplay;
1195 }
1196
1197 /**
1198 * gmenu_tree_directory_get_tree:
1199 * @directory: A #GMenuTreeDirectory
1200 *
1201 * Grab the tree associated with a #GMenuTreeItem.
1202 *
1203 * Returns: (transfer full): The #GMenuTree
1204 */
1205 GMenuTree *
gmenu_tree_directory_get_tree(GMenuTreeDirectory * directory)1206 gmenu_tree_directory_get_tree (GMenuTreeDirectory *directory)
1207 {
1208 g_return_val_if_fail (directory != NULL, NULL);
1209
1210 return g_object_ref (directory->item.tree);
1211 }
1212
1213 static void
append_directory_path(GMenuTreeDirectory * directory,GString * path)1214 append_directory_path (GMenuTreeDirectory *directory,
1215 GString *path)
1216 {
1217
1218 if (!directory->item.parent)
1219 {
1220 g_string_append_c (path, G_DIR_SEPARATOR);
1221 return;
1222 }
1223
1224 append_directory_path (directory->item.parent, path);
1225
1226 g_string_append (path, directory->name);
1227 g_string_append_c (path, G_DIR_SEPARATOR);
1228 }
1229
1230 char *
gmenu_tree_directory_make_path(GMenuTreeDirectory * directory,GMenuTreeEntry * entry)1231 gmenu_tree_directory_make_path (GMenuTreeDirectory *directory,
1232 GMenuTreeEntry *entry)
1233 {
1234 GString *path;
1235
1236 g_return_val_if_fail (directory != NULL, NULL);
1237
1238 path = g_string_new (NULL);
1239
1240 append_directory_path (directory, path);
1241
1242 if (entry != NULL)
1243 g_string_append (path,
1244 desktop_entry_get_basename (entry->desktop_entry));
1245
1246 return g_string_free (path, FALSE);
1247 }
1248
1249 /**
1250 * gmenu_tree_entry_get_app_info:
1251 * @entry: a #GMenuTreeEntry
1252 *
1253 * Returns: (transfer none): The #GDesktopAppInfo for this entry
1254 */
1255 GDesktopAppInfo *
gmenu_tree_entry_get_app_info(GMenuTreeEntry * entry)1256 gmenu_tree_entry_get_app_info (GMenuTreeEntry *entry)
1257 {
1258 g_return_val_if_fail (entry != NULL, NULL);
1259
1260 return desktop_entry_get_app_info (entry->desktop_entry);
1261 }
1262
1263 const char *
gmenu_tree_entry_get_desktop_file_path(GMenuTreeEntry * entry)1264 gmenu_tree_entry_get_desktop_file_path (GMenuTreeEntry *entry)
1265 {
1266 g_return_val_if_fail (entry != NULL, NULL);
1267
1268 return desktop_entry_get_path (entry->desktop_entry);
1269 }
1270
1271 const char *
gmenu_tree_entry_get_desktop_file_id(GMenuTreeEntry * entry)1272 gmenu_tree_entry_get_desktop_file_id (GMenuTreeEntry *entry)
1273 {
1274 g_return_val_if_fail (entry != NULL, NULL);
1275
1276 return entry->desktop_file_id;
1277 }
1278
1279 gboolean
gmenu_tree_entry_get_is_nodisplay_recurse(GMenuTreeEntry * entry)1280 gmenu_tree_entry_get_is_nodisplay_recurse (GMenuTreeEntry *entry)
1281 {
1282 GMenuTreeDirectory *directory;
1283 GDesktopAppInfo *app_info;
1284
1285 g_return_val_if_fail (entry != NULL, FALSE);
1286
1287 app_info = gmenu_tree_entry_get_app_info (entry);
1288
1289 if (g_desktop_app_info_get_nodisplay (app_info))
1290 return TRUE;
1291
1292 directory = entry->item.parent;
1293 while (directory != NULL)
1294 {
1295 if (directory->is_nodisplay)
1296 return TRUE;
1297
1298 directory = directory->item.parent;
1299 }
1300
1301 return FALSE;
1302 }
1303
1304 gboolean
gmenu_tree_entry_get_is_excluded(GMenuTreeEntry * entry)1305 gmenu_tree_entry_get_is_excluded (GMenuTreeEntry *entry)
1306 {
1307 g_return_val_if_fail (entry != NULL, FALSE);
1308
1309 return entry->is_excluded;
1310 }
1311
1312 gboolean
gmenu_tree_entry_get_is_unallocated(GMenuTreeEntry * entry)1313 gmenu_tree_entry_get_is_unallocated (GMenuTreeEntry *entry)
1314 {
1315 g_return_val_if_fail (entry != NULL, FALSE);
1316
1317 return entry->is_unallocated;
1318 }
1319
1320 /**
1321 * gmenu_tree_entry_get_tree:
1322 * @entry: A #GMenuTreeEntry
1323 *
1324 * Grab the tree associated with a #GMenuTreeEntry.
1325 *
1326 * Returns: (transfer full): The #GMenuTree
1327 */
1328 GMenuTree *
gmenu_tree_entry_get_tree(GMenuTreeEntry * entry)1329 gmenu_tree_entry_get_tree (GMenuTreeEntry *entry)
1330 {
1331 g_return_val_if_fail (entry != NULL, NULL);
1332
1333 return g_object_ref (entry->item.tree);
1334 }
1335
1336 GMenuTreeDirectory *
gmenu_tree_header_get_directory(GMenuTreeHeader * header)1337 gmenu_tree_header_get_directory (GMenuTreeHeader *header)
1338 {
1339 g_return_val_if_fail (header != NULL, NULL);
1340
1341 return gmenu_tree_item_ref (header->directory);
1342 }
1343
1344 /**
1345 * gmenu_tree_header_get_tree:
1346 * @header: A #GMenuTreeHeader
1347 *
1348 * Grab the tree associated with a #GMenuTreeHeader.
1349 *
1350 * Returns: (transfer full): The #GMenuTree
1351 */
1352 GMenuTree *
gmenu_tree_header_get_tree(GMenuTreeHeader * header)1353 gmenu_tree_header_get_tree (GMenuTreeHeader *header)
1354 {
1355 g_return_val_if_fail (header != NULL, NULL);
1356
1357 return g_object_ref (header->item.tree);
1358 }
1359
1360 GMenuTreeItemType
gmenu_tree_alias_get_aliased_item_type(GMenuTreeAlias * alias)1361 gmenu_tree_alias_get_aliased_item_type (GMenuTreeAlias *alias)
1362 {
1363 g_return_val_if_fail (alias != NULL, GMENU_TREE_ITEM_INVALID);
1364
1365 g_assert (alias->aliased_item != NULL);
1366 return alias->aliased_item->type;
1367 }
1368
1369 GMenuTreeDirectory *
gmenu_tree_alias_get_directory(GMenuTreeAlias * alias)1370 gmenu_tree_alias_get_directory (GMenuTreeAlias *alias)
1371 {
1372 g_return_val_if_fail (alias != NULL, NULL);
1373
1374 return gmenu_tree_item_ref (alias->directory);
1375 }
1376
1377 /**
1378 * gmenu_tree_alias_get_tree:
1379 * @alias: A #GMenuTreeAlias
1380 *
1381 * Grab the tree associated with a #GMenuTreeAlias.
1382 *
1383 * Returns: (transfer full): The #GMenuTree
1384 */
1385 GMenuTree *
gmenu_tree_alias_get_tree(GMenuTreeAlias * alias)1386 gmenu_tree_alias_get_tree (GMenuTreeAlias *alias)
1387 {
1388 g_return_val_if_fail (alias != NULL, NULL);
1389
1390 return g_object_ref (alias->item.tree);
1391 }
1392
1393 /**
1394 * gmenu_tree_separator_get_tree:
1395 * @separator: A #GMenuTreeSeparator
1396 *
1397 * Grab the tree associated with a #GMenuTreeSeparator.
1398 *
1399 * Returns: (transfer full): The #GMenuTree
1400 */
1401 GMenuTree *
gmenu_tree_separator_get_tree(GMenuTreeSeparator * separator)1402 gmenu_tree_separator_get_tree (GMenuTreeSeparator *separator)
1403 {
1404 g_return_val_if_fail (separator != NULL, NULL);
1405
1406 return g_object_ref (separator->item.tree);
1407 }
1408
1409 /**
1410 * gmenu_tree_alias_get_aliased_directory:
1411 * @alias: alias
1412 *
1413 * Returns: (transfer full): The aliased directory entry
1414 */
1415 GMenuTreeDirectory *
gmenu_tree_alias_get_aliased_directory(GMenuTreeAlias * alias)1416 gmenu_tree_alias_get_aliased_directory (GMenuTreeAlias *alias)
1417 {
1418 g_return_val_if_fail (alias != NULL, NULL);
1419 g_return_val_if_fail (alias->aliased_item->type == GMENU_TREE_ITEM_DIRECTORY, NULL);
1420
1421 return (GMenuTreeDirectory *) gmenu_tree_item_ref (alias->aliased_item);
1422 }
1423
1424 /**
1425 * gmenu_tree_alias_get_aliased_entry:
1426 * @alias: alias
1427 *
1428 * Returns: (transfer full): The aliased entry
1429 */
1430 GMenuTreeEntry *
gmenu_tree_alias_get_aliased_entry(GMenuTreeAlias * alias)1431 gmenu_tree_alias_get_aliased_entry (GMenuTreeAlias *alias)
1432 {
1433 g_return_val_if_fail (alias != NULL, NULL);
1434 g_return_val_if_fail (alias->aliased_item->type == GMENU_TREE_ITEM_ENTRY, NULL);
1435
1436 return (GMenuTreeEntry *) gmenu_tree_item_ref (alias->aliased_item);
1437 }
1438
1439 static GMenuTreeDirectory *
gmenu_tree_directory_new(GMenuTree * tree,GMenuTreeDirectory * parent,const char * name)1440 gmenu_tree_directory_new (GMenuTree *tree,
1441 GMenuTreeDirectory *parent,
1442 const char *name)
1443 {
1444 GMenuTreeDirectory *retval;
1445
1446 retval = g_slice_new0 (GMenuTreeDirectory);
1447
1448 retval->item.type = GMENU_TREE_ITEM_DIRECTORY;
1449 retval->item.parent = parent;
1450 retval->item.refcount = 1;
1451 retval->item.tree = tree;
1452
1453 retval->name = g_strdup (name);
1454 retval->directory_entry = NULL;
1455 retval->entries = NULL;
1456 retval->subdirs = NULL;
1457 retval->default_layout_info = NULL;
1458 retval->layout_info = NULL;
1459 retval->contents = NULL;
1460 retval->only_unallocated = FALSE;
1461 retval->is_nodisplay = FALSE;
1462 retval->layout_pending_separator = FALSE;
1463 retval->preprocessed = FALSE;
1464 retval->will_inline_header = G_MAXUINT16;
1465
1466 retval->default_layout_values.mask = MENU_LAYOUT_VALUES_NONE;
1467 retval->default_layout_values.show_empty = FALSE;
1468 retval->default_layout_values.inline_menus = FALSE;
1469 retval->default_layout_values.inline_limit = 4;
1470 retval->default_layout_values.inline_header = FALSE;
1471 retval->default_layout_values.inline_alias = FALSE;
1472
1473 return retval;
1474 }
1475
1476 static void
gmenu_tree_directory_finalize(GMenuTreeDirectory * directory)1477 gmenu_tree_directory_finalize (GMenuTreeDirectory *directory)
1478 {
1479 g_assert (directory->item.refcount == 0);
1480
1481 g_slist_foreach (directory->contents,
1482 (GFunc) gmenu_tree_item_unref_and_unset_parent,
1483 NULL);
1484 g_slist_free (directory->contents);
1485 directory->contents = NULL;
1486
1487 g_slist_foreach (directory->default_layout_info,
1488 (GFunc) menu_layout_node_unref,
1489 NULL);
1490 g_slist_free (directory->default_layout_info);
1491 directory->default_layout_info = NULL;
1492
1493 g_slist_foreach (directory->layout_info,
1494 (GFunc) menu_layout_node_unref,
1495 NULL);
1496 g_slist_free (directory->layout_info);
1497 directory->layout_info = NULL;
1498
1499 g_slist_foreach (directory->subdirs,
1500 (GFunc) gmenu_tree_item_unref_and_unset_parent,
1501 NULL);
1502 g_slist_free (directory->subdirs);
1503 directory->subdirs = NULL;
1504
1505 g_slist_foreach (directory->entries,
1506 (GFunc) gmenu_tree_item_unref_and_unset_parent,
1507 NULL);
1508 g_slist_free (directory->entries);
1509 directory->entries = NULL;
1510
1511 if (directory->directory_entry)
1512 desktop_entry_unref (directory->directory_entry);
1513 directory->directory_entry = NULL;
1514
1515 g_free (directory->name);
1516 directory->name = NULL;
1517
1518 g_slice_free (GMenuTreeDirectory, directory);
1519 }
1520
1521 static GMenuTreeSeparator *
gmenu_tree_separator_new(GMenuTreeDirectory * parent)1522 gmenu_tree_separator_new (GMenuTreeDirectory *parent)
1523 {
1524 GMenuTreeSeparator *retval;
1525
1526 retval = g_slice_new0 (GMenuTreeSeparator);
1527
1528 retval->item.type = GMENU_TREE_ITEM_SEPARATOR;
1529 retval->item.parent = parent;
1530 retval->item.refcount = 1;
1531 retval->item.tree = parent->item.tree;
1532
1533 return retval;
1534 }
1535
1536 static void
gmenu_tree_separator_finalize(GMenuTreeSeparator * separator)1537 gmenu_tree_separator_finalize (GMenuTreeSeparator *separator)
1538 {
1539 g_assert (separator->item.refcount == 0);
1540
1541 g_slice_free (GMenuTreeSeparator, separator);
1542 }
1543
1544 static GMenuTreeHeader *
gmenu_tree_header_new(GMenuTreeDirectory * parent,GMenuTreeDirectory * directory)1545 gmenu_tree_header_new (GMenuTreeDirectory *parent,
1546 GMenuTreeDirectory *directory)
1547 {
1548 GMenuTreeHeader *retval;
1549
1550 retval = g_slice_new0 (GMenuTreeHeader);
1551
1552 retval->item.type = GMENU_TREE_ITEM_HEADER;
1553 retval->item.parent = parent;
1554 retval->item.refcount = 1;
1555 retval->item.tree = parent->item.tree;
1556
1557 retval->directory = gmenu_tree_item_ref (directory);
1558
1559 gmenu_tree_item_set_parent (GMENU_TREE_ITEM (retval->directory), NULL);
1560
1561 return retval;
1562 }
1563
1564 static void
gmenu_tree_header_finalize(GMenuTreeHeader * header)1565 gmenu_tree_header_finalize (GMenuTreeHeader *header)
1566 {
1567 g_assert (header->item.refcount == 0);
1568
1569 if (header->directory != NULL)
1570 gmenu_tree_item_unref (header->directory);
1571 header->directory = NULL;
1572
1573 g_slice_free (GMenuTreeHeader, header);
1574 }
1575
1576 static GMenuTreeAlias *
gmenu_tree_alias_new(GMenuTreeDirectory * parent,GMenuTreeDirectory * directory,GMenuTreeItem * item)1577 gmenu_tree_alias_new (GMenuTreeDirectory *parent,
1578 GMenuTreeDirectory *directory,
1579 GMenuTreeItem *item)
1580 {
1581 GMenuTreeAlias *retval;
1582
1583 retval = g_slice_new0 (GMenuTreeAlias);
1584
1585 retval->item.type = GMENU_TREE_ITEM_ALIAS;
1586 retval->item.parent = parent;
1587 retval->item.refcount = 1;
1588 retval->item.tree = parent->item.tree;
1589
1590 retval->directory = gmenu_tree_item_ref (directory);
1591 if (item->type != GMENU_TREE_ITEM_ALIAS)
1592 retval->aliased_item = gmenu_tree_item_ref (item);
1593 else
1594 {
1595 GMenuTreeAlias *alias = GMENU_TREE_ALIAS (item);
1596 retval->aliased_item = gmenu_tree_item_ref (alias->aliased_item);
1597 }
1598
1599 gmenu_tree_item_set_parent (GMENU_TREE_ITEM (retval->directory), NULL);
1600 gmenu_tree_item_set_parent (retval->aliased_item, NULL);
1601
1602 return retval;
1603 }
1604
1605 static void
gmenu_tree_alias_finalize(GMenuTreeAlias * alias)1606 gmenu_tree_alias_finalize (GMenuTreeAlias *alias)
1607 {
1608 g_assert (alias->item.refcount == 0);
1609
1610 if (alias->directory != NULL)
1611 gmenu_tree_item_unref (alias->directory);
1612 alias->directory = NULL;
1613
1614 if (alias->aliased_item != NULL)
1615 gmenu_tree_item_unref (alias->aliased_item);
1616 alias->aliased_item = NULL;
1617
1618 g_slice_free (GMenuTreeAlias, alias);
1619 }
1620
1621 static GMenuTreeEntry *
gmenu_tree_entry_new(GMenuTreeDirectory * parent,DesktopEntry * desktop_entry,const char * desktop_file_id,gboolean is_excluded,gboolean is_unallocated)1622 gmenu_tree_entry_new (GMenuTreeDirectory *parent,
1623 DesktopEntry *desktop_entry,
1624 const char *desktop_file_id,
1625 gboolean is_excluded,
1626 gboolean is_unallocated)
1627 {
1628 GMenuTreeEntry *retval;
1629
1630 retval = g_slice_new0 (GMenuTreeEntry);
1631
1632 retval->item.type = GMENU_TREE_ITEM_ENTRY;
1633 retval->item.parent = parent;
1634 retval->item.refcount = 1;
1635 retval->item.tree = parent->item.tree;
1636
1637 retval->desktop_entry = desktop_entry_ref (desktop_entry);
1638 retval->desktop_file_id = g_strdup (desktop_file_id);
1639 retval->is_excluded = is_excluded != FALSE;
1640 retval->is_unallocated = is_unallocated != FALSE;
1641
1642 return retval;
1643 }
1644
1645 static void
gmenu_tree_entry_finalize(GMenuTreeEntry * entry)1646 gmenu_tree_entry_finalize (GMenuTreeEntry *entry)
1647 {
1648 g_assert (entry->item.refcount == 0);
1649
1650 g_free (entry->desktop_file_id);
1651 entry->desktop_file_id = NULL;
1652
1653 if (entry->desktop_entry)
1654 desktop_entry_unref (entry->desktop_entry);
1655 entry->desktop_entry = NULL;
1656
1657 g_slice_free (GMenuTreeEntry, entry);
1658 }
1659
1660 static int
gmenu_tree_entry_compare_by_id(GMenuTreeItem * a,GMenuTreeItem * b)1661 gmenu_tree_entry_compare_by_id (GMenuTreeItem *a,
1662 GMenuTreeItem *b)
1663 {
1664 if (a->type == GMENU_TREE_ITEM_ALIAS)
1665 a = GMENU_TREE_ALIAS (a)->aliased_item;
1666
1667 if (b->type == GMENU_TREE_ITEM_ALIAS)
1668 b = GMENU_TREE_ALIAS (b)->aliased_item;
1669
1670 return strcmp (GMENU_TREE_ENTRY (a)->desktop_file_id,
1671 GMENU_TREE_ENTRY (b)->desktop_file_id);
1672 }
1673
1674 /**
1675 * gmenu_tree_item_ref:
1676 * @item: a #GMenuTreeItem
1677 *
1678 * Returns: (transfer full): The same @item, or %NULL if @item is not a valid #GMenuTreeItem
1679 */
1680 gpointer
gmenu_tree_item_ref(gpointer itemp)1681 gmenu_tree_item_ref (gpointer itemp)
1682 {
1683 GMenuTreeItem *item;
1684
1685 item = (GMenuTreeItem *) itemp;
1686
1687 g_return_val_if_fail (item != NULL, NULL);
1688 g_return_val_if_fail (item->refcount > 0, NULL);
1689
1690 g_atomic_int_inc (&item->refcount);
1691
1692 return item;
1693 }
1694
1695 void
gmenu_tree_item_unref(gpointer itemp)1696 gmenu_tree_item_unref (gpointer itemp)
1697 {
1698 GMenuTreeItem *item;
1699
1700 item = (GMenuTreeItem *) itemp;
1701
1702 g_return_if_fail (item != NULL);
1703 g_return_if_fail (item->refcount > 0);
1704
1705 if (g_atomic_int_dec_and_test (&(item->refcount)))
1706 {
1707 switch (item->type)
1708 {
1709 case GMENU_TREE_ITEM_DIRECTORY:
1710 gmenu_tree_directory_finalize (GMENU_TREE_DIRECTORY (item));
1711 break;
1712
1713 case GMENU_TREE_ITEM_ENTRY:
1714 gmenu_tree_entry_finalize (GMENU_TREE_ENTRY (item));
1715 break;
1716
1717 case GMENU_TREE_ITEM_SEPARATOR:
1718 gmenu_tree_separator_finalize (GMENU_TREE_SEPARATOR (item));
1719 break;
1720
1721 case GMENU_TREE_ITEM_HEADER:
1722 gmenu_tree_header_finalize (GMENU_TREE_HEADER (item));
1723 break;
1724
1725 case GMENU_TREE_ITEM_ALIAS:
1726 gmenu_tree_alias_finalize (GMENU_TREE_ALIAS (item));
1727 break;
1728
1729 default:
1730 g_assert_not_reached ();
1731 break;
1732 }
1733 }
1734 }
1735
1736 static void
gmenu_tree_item_unref_and_unset_parent(gpointer itemp)1737 gmenu_tree_item_unref_and_unset_parent (gpointer itemp)
1738 {
1739 GMenuTreeItem *item;
1740
1741 item = (GMenuTreeItem *) itemp;
1742
1743 g_return_if_fail (item != NULL);
1744
1745 gmenu_tree_item_set_parent (item, NULL);
1746 gmenu_tree_item_unref (item);
1747 }
1748
1749 static inline const char *
gmenu_tree_item_compare_get_name_helper(GMenuTreeItem * item,GMenuTreeFlags flags)1750 gmenu_tree_item_compare_get_name_helper (GMenuTreeItem *item,
1751 GMenuTreeFlags flags)
1752 {
1753 const char *name;
1754
1755 name = NULL;
1756
1757 switch (item->type)
1758 {
1759 case GMENU_TREE_ITEM_DIRECTORY:
1760 if (GMENU_TREE_DIRECTORY (item)->directory_entry)
1761 name = desktop_entry_get_name (GMENU_TREE_DIRECTORY (item)->directory_entry);
1762 else
1763 name = GMENU_TREE_DIRECTORY (item)->name;
1764 break;
1765
1766 case GMENU_TREE_ITEM_ENTRY:
1767 if (flags & GMENU_TREE_FLAGS_SORT_DISPLAY_NAME)
1768 name = g_app_info_get_display_name (G_APP_INFO (gmenu_tree_entry_get_app_info (GMENU_TREE_ENTRY (item))));
1769 else
1770 name = desktop_entry_get_name (GMENU_TREE_ENTRY (item)->desktop_entry);
1771 break;
1772
1773 case GMENU_TREE_ITEM_ALIAS:
1774 {
1775 GMenuTreeItem *dir;
1776 dir = GMENU_TREE_ITEM (GMENU_TREE_ALIAS (item)->directory);
1777 name = gmenu_tree_item_compare_get_name_helper (dir, flags);
1778 }
1779 break;
1780
1781 case GMENU_TREE_ITEM_SEPARATOR:
1782 case GMENU_TREE_ITEM_HEADER:
1783 default:
1784 g_assert_not_reached ();
1785 break;
1786 }
1787
1788 return name;
1789 }
1790
1791 static int
gmenu_tree_item_compare(GMenuTreeItem * a,GMenuTreeItem * b,gpointer flags_p)1792 gmenu_tree_item_compare (GMenuTreeItem *a,
1793 GMenuTreeItem *b,
1794 gpointer flags_p)
1795 {
1796 const char *name_a;
1797 const char *name_b;
1798 GMenuTreeFlags flags;
1799
1800 flags = GPOINTER_TO_INT (flags_p);
1801
1802 name_a = gmenu_tree_item_compare_get_name_helper (a, flags);
1803 name_b = gmenu_tree_item_compare_get_name_helper (b, flags);
1804
1805 return g_utf8_collate (name_a, name_b);
1806 }
1807
1808 static MenuLayoutNode *
find_menu_child(MenuLayoutNode * layout)1809 find_menu_child (MenuLayoutNode *layout)
1810 {
1811 MenuLayoutNode *child;
1812
1813 child = menu_layout_node_get_children (layout);
1814 while (child && menu_layout_node_get_type (child) != MENU_LAYOUT_NODE_MENU)
1815 child = menu_layout_node_get_next (child);
1816
1817 return child;
1818 }
1819
1820 static void
merge_resolved_children(GMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * where,MenuLayoutNode * from)1821 merge_resolved_children (GMenuTree *tree,
1822 GHashTable *loaded_menu_files,
1823 MenuLayoutNode *where,
1824 MenuLayoutNode *from)
1825 {
1826 MenuLayoutNode *insert_after;
1827 MenuLayoutNode *menu_child;
1828 MenuLayoutNode *from_child;
1829
1830 gmenu_tree_resolve_files (tree, loaded_menu_files, from);
1831
1832 insert_after = where;
1833 g_assert (menu_layout_node_get_type (insert_after) != MENU_LAYOUT_NODE_ROOT);
1834 g_assert (menu_layout_node_get_parent (insert_after) != NULL);
1835
1836 /* skip root node */
1837 menu_child = find_menu_child (from);
1838 g_assert (menu_child != NULL);
1839 g_assert (menu_layout_node_get_type (menu_child) == MENU_LAYOUT_NODE_MENU);
1840
1841 /* merge children of toplevel <Menu> */
1842 from_child = menu_layout_node_get_children (menu_child);
1843 while (from_child != NULL)
1844 {
1845 MenuLayoutNode *next;
1846
1847 next = menu_layout_node_get_next (from_child);
1848
1849 menu_verbose ("Merging ");
1850 menu_debug_print_layout (from_child, FALSE);
1851 menu_verbose (" after ");
1852 menu_debug_print_layout (insert_after, FALSE);
1853
1854 switch (menu_layout_node_get_type (from_child))
1855 {
1856 case MENU_LAYOUT_NODE_NAME:
1857 menu_layout_node_unlink (from_child); /* delete this */
1858 break;
1859
1860 default:
1861 menu_layout_node_steal (from_child);
1862 menu_layout_node_insert_after (insert_after, from_child);
1863 menu_layout_node_unref (from_child);
1864
1865 insert_after = from_child;
1866 break;
1867 }
1868
1869 from_child = next;
1870 }
1871 }
1872
1873 static gboolean
load_merge_file(GMenuTree * tree,GHashTable * loaded_menu_files,const char * filename,gboolean is_canonical,gboolean add_monitor,MenuLayoutNode * where)1874 load_merge_file (GMenuTree *tree,
1875 GHashTable *loaded_menu_files,
1876 const char *filename,
1877 gboolean is_canonical,
1878 gboolean add_monitor,
1879 MenuLayoutNode *where)
1880 {
1881 MenuLayoutNode *to_merge;
1882 const char *canonical;
1883 char *freeme;
1884 gboolean retval;
1885
1886 freeme = NULL;
1887 retval = FALSE;
1888
1889 if (!is_canonical)
1890 {
1891 canonical = freeme = realpath (filename, NULL);
1892 if (canonical == NULL)
1893 {
1894 if (add_monitor)
1895 gmenu_tree_add_menu_file_monitor (tree,
1896 filename,
1897 MENU_FILE_MONITOR_NONEXISTENT_FILE);
1898
1899 menu_verbose ("Failed to canonicalize merge file path \"%s\": %s\n",
1900 filename, g_strerror (errno));
1901 goto out;
1902 }
1903 }
1904 else
1905 {
1906 canonical = filename;
1907 }
1908
1909 if (g_hash_table_lookup (loaded_menu_files, canonical) != NULL)
1910 {
1911 g_warning ("Not loading \"%s\": recursive loop detected in .menu files",
1912 canonical);
1913 retval = TRUE;
1914 goto out;
1915 }
1916
1917 menu_verbose ("Merging file \"%s\"\n", canonical);
1918
1919 to_merge = menu_layout_load (canonical, tree->non_prefixed_basename, NULL);
1920 if (to_merge == NULL)
1921 {
1922 menu_verbose ("No menu for file \"%s\" found when merging\n",
1923 canonical);
1924 goto out;
1925 }
1926
1927 retval = TRUE;
1928
1929 g_hash_table_insert (loaded_menu_files, (char *) canonical, GUINT_TO_POINTER (TRUE));
1930
1931 if (add_monitor)
1932 gmenu_tree_add_menu_file_monitor (tree,
1933 canonical,
1934 MENU_FILE_MONITOR_FILE);
1935
1936 merge_resolved_children (tree, loaded_menu_files, where, to_merge);
1937
1938 g_hash_table_remove (loaded_menu_files, canonical);
1939
1940 menu_layout_node_unref (to_merge);
1941
1942 out:
1943 if (freeme)
1944 g_free (freeme);
1945
1946 return retval;
1947 }
1948
1949 static gboolean
load_merge_file_with_config_dir(GMenuTree * tree,GHashTable * loaded_menu_files,const char * menu_file,const char * config_dir,MenuLayoutNode * where)1950 load_merge_file_with_config_dir (GMenuTree *tree,
1951 GHashTable *loaded_menu_files,
1952 const char *menu_file,
1953 const char *config_dir,
1954 MenuLayoutNode *where)
1955 {
1956 char *merge_file;
1957 gboolean loaded;
1958
1959 loaded = FALSE;
1960
1961 merge_file = g_build_filename (config_dir, "menus", menu_file, NULL);
1962
1963 if (load_merge_file (tree, loaded_menu_files, merge_file, FALSE, TRUE, where))
1964 loaded = TRUE;
1965
1966 g_free (merge_file);
1967
1968 return loaded;
1969 }
1970
1971 static gboolean
compare_basedir_to_config_dir(const char * canonical_basedir,const char * config_dir)1972 compare_basedir_to_config_dir (const char *canonical_basedir,
1973 const char *config_dir)
1974 {
1975 char *dirname;
1976 char *canonical_menus_dir;
1977 gboolean retval;
1978
1979 menu_verbose ("Checking to see if basedir '%s' is in '%s'\n",
1980 canonical_basedir, config_dir);
1981
1982 dirname = g_build_filename (config_dir, "menus", NULL);
1983
1984 retval = FALSE;
1985
1986 canonical_menus_dir = realpath (dirname, NULL);
1987 if (canonical_menus_dir != NULL &&
1988 strcmp (canonical_basedir, canonical_menus_dir) == 0)
1989 {
1990 retval = TRUE;
1991 }
1992
1993 g_free (canonical_menus_dir);
1994 g_free (dirname);
1995
1996 return retval;
1997 }
1998
1999 static gboolean
load_parent_merge_file_from_basename(GMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * layout,const char * menu_file,const char * canonical_basedir)2000 load_parent_merge_file_from_basename (GMenuTree *tree,
2001 GHashTable *loaded_menu_files,
2002 MenuLayoutNode *layout,
2003 const char *menu_file,
2004 const char *canonical_basedir)
2005 {
2006 gboolean found_basedir;
2007 const char * const *system_config_dirs;
2008 int i;
2009
2010 /* We're not interested in menu files that are in directories which are not a
2011 * parent of the base directory of this menu file */
2012 found_basedir = compare_basedir_to_config_dir (canonical_basedir,
2013 g_get_user_config_dir ());
2014
2015 system_config_dirs = g_get_system_config_dirs ();
2016
2017 i = 0;
2018 while (system_config_dirs[i] != NULL)
2019 {
2020 if (!found_basedir)
2021 {
2022 found_basedir = compare_basedir_to_config_dir (canonical_basedir,
2023 system_config_dirs[i]);
2024 }
2025 else
2026 {
2027 menu_verbose ("Looking for parent menu file '%s' in '%s'\n",
2028 menu_file, system_config_dirs[i]);
2029
2030 if (load_merge_file_with_config_dir (tree,
2031 loaded_menu_files,
2032 menu_file,
2033 system_config_dirs[i],
2034 layout))
2035 {
2036 break;
2037 }
2038 }
2039
2040 ++i;
2041 }
2042
2043 return system_config_dirs[i] != NULL;
2044 }
2045
2046 static gboolean
load_parent_merge_file(GMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * layout)2047 load_parent_merge_file (GMenuTree *tree,
2048 GHashTable *loaded_menu_files,
2049 MenuLayoutNode *layout)
2050 {
2051 MenuLayoutNode *root;
2052 const char *basedir;
2053 const char *menu_name;
2054 char *canonical_basedir;
2055 char *menu_file;
2056 gboolean found;
2057
2058 root = menu_layout_node_get_root (layout);
2059
2060 basedir = menu_layout_node_root_get_basedir (root);
2061 menu_name = menu_layout_node_root_get_name (root);
2062
2063 canonical_basedir = realpath (basedir, NULL);
2064 if (canonical_basedir == NULL)
2065 {
2066 menu_verbose ("Menu basedir '%s' no longer exists, not merging parent\n",
2067 basedir);
2068 return FALSE;
2069 }
2070
2071 found = FALSE;
2072 menu_file = g_strconcat (menu_name, ".menu", NULL);
2073
2074 if (strcmp (menu_file, "applications.menu") == 0 &&
2075 g_getenv ("XDG_MENU_PREFIX"))
2076 {
2077 char *prefixed_basename;
2078 prefixed_basename = g_strdup_printf ("%s%s",
2079 g_getenv ("XDG_MENU_PREFIX"),
2080 menu_file);
2081 found = load_parent_merge_file_from_basename (tree, loaded_menu_files,
2082 layout, prefixed_basename,
2083 canonical_basedir);
2084 g_free (prefixed_basename);
2085 }
2086
2087 if (!found)
2088 {
2089 found = load_parent_merge_file_from_basename (tree, loaded_menu_files,
2090 layout, menu_file,
2091 canonical_basedir);
2092 }
2093
2094 g_free (menu_file);
2095 g_free (canonical_basedir);
2096
2097 return found;
2098 }
2099
2100 static void
load_merge_dir(GMenuTree * tree,GHashTable * loaded_menu_files,const char * dirname,MenuLayoutNode * where)2101 load_merge_dir (GMenuTree *tree,
2102 GHashTable *loaded_menu_files,
2103 const char *dirname,
2104 MenuLayoutNode *where)
2105 {
2106 GDir *dir;
2107 const char *menu_file;
2108
2109 menu_verbose ("Loading merge dir \"%s\"\n", dirname);
2110
2111 gmenu_tree_add_menu_file_monitor (tree,
2112 dirname,
2113 MENU_FILE_MONITOR_DIRECTORY);
2114
2115 if ((dir = g_dir_open (dirname, 0, NULL)) == NULL)
2116 return;
2117
2118 while ((menu_file = g_dir_read_name (dir)))
2119 {
2120 if (g_str_has_suffix (menu_file, ".menu"))
2121 {
2122 char *full_path;
2123
2124 full_path = g_build_filename (dirname, menu_file, NULL);
2125
2126 load_merge_file (tree, loaded_menu_files, full_path, TRUE, FALSE, where);
2127
2128 g_free (full_path);
2129 }
2130 }
2131
2132 g_dir_close (dir);
2133 }
2134
2135 static void
load_merge_dir_with_config_dir(GMenuTree * tree,GHashTable * loaded_menu_files,const char * config_dir,const char * dirname,MenuLayoutNode * where)2136 load_merge_dir_with_config_dir (GMenuTree *tree,
2137 GHashTable *loaded_menu_files,
2138 const char *config_dir,
2139 const char *dirname,
2140 MenuLayoutNode *where)
2141 {
2142 char *path;
2143
2144 path = g_build_filename (config_dir, "menus", dirname, NULL);
2145
2146 load_merge_dir (tree, loaded_menu_files, path, where);
2147
2148 g_free (path);
2149 }
2150
2151 static void
resolve_merge_file(GMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * layout)2152 resolve_merge_file (GMenuTree *tree,
2153 GHashTable *loaded_menu_files,
2154 MenuLayoutNode *layout)
2155 {
2156 char *filename;
2157
2158 if (menu_layout_node_merge_file_get_type (layout) == MENU_MERGE_FILE_TYPE_PARENT)
2159 {
2160 if (load_parent_merge_file (tree, loaded_menu_files, layout))
2161 return;
2162 }
2163
2164 filename = menu_layout_node_get_content_as_path (layout);
2165 if (filename == NULL)
2166 {
2167 menu_verbose ("didn't get node content as a path, not merging file\n");
2168 }
2169 else
2170 {
2171 load_merge_file (tree, loaded_menu_files, filename, FALSE, TRUE, layout);
2172
2173 g_free (filename);
2174 }
2175
2176 /* remove the now-replaced node */
2177 menu_layout_node_unlink (layout);
2178 }
2179
2180 static void
resolve_merge_dir(GMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * layout)2181 resolve_merge_dir (GMenuTree *tree,
2182 GHashTable *loaded_menu_files,
2183 MenuLayoutNode *layout)
2184 {
2185 char *path;
2186
2187 path = menu_layout_node_get_content_as_path (layout);
2188 if (path == NULL)
2189 {
2190 menu_verbose ("didn't get layout node content as a path, not merging dir\n");
2191 }
2192 else
2193 {
2194 load_merge_dir (tree, loaded_menu_files, path, layout);
2195
2196 g_free (path);
2197 }
2198
2199 /* remove the now-replaced node */
2200 menu_layout_node_unlink (layout);
2201 }
2202
2203 static MenuLayoutNode *
add_app_dir(GMenuTree * tree,MenuLayoutNode * before,const char * data_dir)2204 add_app_dir (GMenuTree *tree,
2205 MenuLayoutNode *before,
2206 const char *data_dir)
2207 {
2208 MenuLayoutNode *tmp;
2209 char *dirname;
2210
2211 tmp = menu_layout_node_new (MENU_LAYOUT_NODE_APP_DIR);
2212 dirname = g_build_filename (data_dir, "applications", NULL);
2213 menu_layout_node_set_content (tmp, dirname);
2214 menu_layout_node_insert_before (before, tmp);
2215 menu_layout_node_unref (before);
2216
2217 menu_verbose ("Adding <AppDir>%s</AppDir> in <DefaultAppDirs/>\n",
2218 dirname);
2219
2220 g_free (dirname);
2221
2222 return tmp;
2223 }
2224
2225 static void
resolve_default_app_dirs(GMenuTree * tree,MenuLayoutNode * layout)2226 resolve_default_app_dirs (GMenuTree *tree,
2227 MenuLayoutNode *layout)
2228 {
2229 MenuLayoutNode *before;
2230 const char * const *system_data_dirs;
2231 int i;
2232
2233 system_data_dirs = g_get_system_data_dirs ();
2234
2235 before = add_app_dir (tree,
2236 menu_layout_node_ref (layout),
2237 g_get_user_data_dir ());
2238
2239 i = 0;
2240 while (system_data_dirs[i] != NULL)
2241 {
2242 before = add_app_dir (tree, before, system_data_dirs[i]);
2243
2244 ++i;
2245 }
2246
2247 menu_layout_node_unref (before);
2248
2249 /* remove the now-replaced node */
2250 menu_layout_node_unlink (layout);
2251 }
2252
2253 static MenuLayoutNode *
add_directory_dir(GMenuTree * tree,MenuLayoutNode * before,const char * data_dir)2254 add_directory_dir (GMenuTree *tree,
2255 MenuLayoutNode *before,
2256 const char *data_dir)
2257 {
2258 MenuLayoutNode *tmp;
2259 char *dirname;
2260
2261 tmp = menu_layout_node_new (MENU_LAYOUT_NODE_DIRECTORY_DIR);
2262 dirname = g_build_filename (data_dir, "desktop-directories", NULL);
2263 menu_layout_node_set_content (tmp, dirname);
2264 menu_layout_node_insert_before (before, tmp);
2265 menu_layout_node_unref (before);
2266
2267 menu_verbose ("Adding <DirectoryDir>%s</DirectoryDir> in <DefaultDirectoryDirs/>\n",
2268 dirname);
2269
2270 g_free (dirname);
2271
2272 return tmp;
2273 }
2274
2275 static void
resolve_default_directory_dirs(GMenuTree * tree,MenuLayoutNode * layout)2276 resolve_default_directory_dirs (GMenuTree *tree,
2277 MenuLayoutNode *layout)
2278 {
2279 MenuLayoutNode *before;
2280 const char * const *system_data_dirs;
2281 int i;
2282
2283 system_data_dirs = g_get_system_data_dirs ();
2284
2285 before = add_directory_dir (tree,
2286 menu_layout_node_ref (layout),
2287 g_get_user_data_dir ());
2288
2289 i = 0;
2290 while (system_data_dirs[i] != NULL)
2291 {
2292 before = add_directory_dir (tree, before, system_data_dirs[i]);
2293
2294 ++i;
2295 }
2296
2297 menu_layout_node_unref (before);
2298
2299 /* remove the now-replaced node */
2300 menu_layout_node_unlink (layout);
2301 }
2302
2303 static void
resolve_default_merge_dirs(GMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * layout)2304 resolve_default_merge_dirs (GMenuTree *tree,
2305 GHashTable *loaded_menu_files,
2306 MenuLayoutNode *layout)
2307 {
2308 MenuLayoutNode *root;
2309 const char *menu_name;
2310 char *merge_name;
2311 const char * const *system_config_dirs;
2312 int i;
2313
2314 root = menu_layout_node_get_root (layout);
2315 menu_name = menu_layout_node_root_get_name (root);
2316
2317 merge_name = g_strconcat (menu_name, "-merged", NULL);
2318
2319 system_config_dirs = g_get_system_config_dirs ();
2320
2321 /* Merge in reverse order */
2322 i = 0;
2323 while (system_config_dirs[i] != NULL) i++;
2324 while (i > 0)
2325 {
2326 i--;
2327 load_merge_dir_with_config_dir (tree,
2328 loaded_menu_files,
2329 system_config_dirs[i],
2330 merge_name,
2331 layout);
2332 }
2333
2334 load_merge_dir_with_config_dir (tree,
2335 loaded_menu_files,
2336 g_get_user_config_dir (),
2337 merge_name,
2338 layout);
2339
2340 g_free (merge_name);
2341
2342 /* remove the now-replaced node */
2343 menu_layout_node_unlink (layout);
2344 }
2345
2346 static void
gmenu_tree_resolve_files(GMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * layout)2347 gmenu_tree_resolve_files (GMenuTree *tree,
2348 GHashTable *loaded_menu_files,
2349 MenuLayoutNode *layout)
2350 {
2351 MenuLayoutNode *child;
2352
2353 menu_verbose ("Resolving files in: ");
2354 menu_debug_print_layout (layout, TRUE);
2355
2356 switch (menu_layout_node_get_type (layout))
2357 {
2358 case MENU_LAYOUT_NODE_MERGE_FILE:
2359 resolve_merge_file (tree, loaded_menu_files, layout);
2360 break;
2361
2362 case MENU_LAYOUT_NODE_MERGE_DIR:
2363 resolve_merge_dir (tree, loaded_menu_files, layout);
2364 break;
2365
2366 case MENU_LAYOUT_NODE_DEFAULT_APP_DIRS:
2367 resolve_default_app_dirs (tree, layout);
2368 break;
2369
2370 case MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS:
2371 resolve_default_directory_dirs (tree, layout);
2372 break;
2373
2374 case MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS:
2375 resolve_default_merge_dirs (tree, loaded_menu_files, layout);
2376 break;
2377
2378 case MENU_LAYOUT_NODE_LEGACY_DIR:
2379 menu_verbose ("Ignoring obsolete legacy dir");
2380 break;
2381
2382 case MENU_LAYOUT_NODE_KDE_LEGACY_DIRS:
2383 menu_verbose ("Ignoring obsolete KDE legacy dirs");
2384 break;
2385
2386 case MENU_LAYOUT_NODE_PASSTHROUGH:
2387 /* Just get rid of these, we don't need the memory usage */
2388 menu_layout_node_unlink (layout);
2389 break;
2390
2391 default:
2392 /* Recurse */
2393 child = menu_layout_node_get_children (layout);
2394 while (child != NULL)
2395 {
2396 MenuLayoutNode *next = menu_layout_node_get_next (child);
2397
2398 gmenu_tree_resolve_files (tree, loaded_menu_files, child);
2399
2400 child = next;
2401 }
2402 break;
2403 }
2404 }
2405
2406 static void
move_children(MenuLayoutNode * from,MenuLayoutNode * to)2407 move_children (MenuLayoutNode *from,
2408 MenuLayoutNode *to)
2409 {
2410 MenuLayoutNode *from_child;
2411 MenuLayoutNode *insert_before;
2412
2413 insert_before = menu_layout_node_get_children (to);
2414 from_child = menu_layout_node_get_children (from);
2415
2416 while (from_child != NULL)
2417 {
2418 MenuLayoutNode *next;
2419
2420 next = menu_layout_node_get_next (from_child);
2421
2422 menu_layout_node_steal (from_child);
2423
2424 if (menu_layout_node_get_type (from_child) == MENU_LAYOUT_NODE_NAME)
2425 {
2426 ; /* just drop the Name in the old <Menu> */
2427 }
2428 else if (insert_before)
2429 {
2430 menu_layout_node_insert_before (insert_before, from_child);
2431 g_assert (menu_layout_node_get_next (from_child) == insert_before);
2432 }
2433 else
2434 {
2435 menu_layout_node_append_child (to, from_child);
2436 }
2437
2438 menu_layout_node_unref (from_child);
2439
2440 from_child = next;
2441 }
2442 }
2443
2444 static int
null_safe_strcmp(const char * a,const char * b)2445 null_safe_strcmp (const char *a,
2446 const char *b)
2447 {
2448 if (a == NULL && b == NULL)
2449 return 0;
2450 else if (a == NULL)
2451 return -1;
2452 else if (b == NULL)
2453 return 1;
2454 else
2455 return strcmp (a, b);
2456 }
2457
2458 static int
node_compare_func(const void * a,const void * b)2459 node_compare_func (const void *a,
2460 const void *b)
2461 {
2462 MenuLayoutNode *node_a = (MenuLayoutNode*) a;
2463 MenuLayoutNode *node_b = (MenuLayoutNode*) b;
2464 MenuLayoutNodeType t_a = menu_layout_node_get_type (node_a);
2465 MenuLayoutNodeType t_b = menu_layout_node_get_type (node_b);
2466
2467 if (t_a < t_b)
2468 return -1;
2469 else if (t_a > t_b)
2470 return 1;
2471 else
2472 {
2473 const char *c_a = menu_layout_node_get_content (node_a);
2474 const char *c_b = menu_layout_node_get_content (node_b);
2475
2476 return null_safe_strcmp (c_a, c_b);
2477 }
2478 }
2479
2480 static int
node_menu_compare_func(const void * a,const void * b)2481 node_menu_compare_func (const void *a,
2482 const void *b)
2483 {
2484 MenuLayoutNode *node_a = (MenuLayoutNode*) a;
2485 MenuLayoutNode *node_b = (MenuLayoutNode*) b;
2486 MenuLayoutNode *parent_a = menu_layout_node_get_parent (node_a);
2487 MenuLayoutNode *parent_b = menu_layout_node_get_parent (node_b);
2488
2489 if (parent_a < parent_b)
2490 return -1;
2491 else if (parent_a > parent_b)
2492 return 1;
2493 else
2494 return null_safe_strcmp (menu_layout_node_menu_get_name (node_a),
2495 menu_layout_node_menu_get_name (node_b));
2496 }
2497
2498 static void
gmenu_tree_strip_duplicate_children(GMenuTree * tree,MenuLayoutNode * layout)2499 gmenu_tree_strip_duplicate_children (GMenuTree *tree,
2500 MenuLayoutNode *layout)
2501 {
2502 MenuLayoutNode *child;
2503 GSList *simple_nodes;
2504 GSList *menu_layout_nodes;
2505 GSList *prev;
2506 GSList *tmp;
2507
2508 /* to strip dups, we find all the child nodes where
2509 * we want to kill dups, sort them,
2510 * then nuke the adjacent nodes that are equal
2511 */
2512
2513 simple_nodes = NULL;
2514 menu_layout_nodes = NULL;
2515
2516 child = menu_layout_node_get_children (layout);
2517 while (child != NULL)
2518 {
2519 switch (menu_layout_node_get_type (child))
2520 {
2521 /* These are dups if their content is the same */
2522 case MENU_LAYOUT_NODE_APP_DIR:
2523 case MENU_LAYOUT_NODE_DIRECTORY_DIR:
2524 case MENU_LAYOUT_NODE_DIRECTORY:
2525 simple_nodes = g_slist_prepend (simple_nodes, child);
2526 break;
2527
2528 /* These have to be merged in a more complicated way,
2529 * and then recursed
2530 */
2531 case MENU_LAYOUT_NODE_MENU:
2532 menu_layout_nodes = g_slist_prepend (menu_layout_nodes, child);
2533 break;
2534
2535 default:
2536 break;
2537 }
2538
2539 child = menu_layout_node_get_next (child);
2540 }
2541
2542 /* Note that the lists are all backward. So we want to keep
2543 * the items that are earlier in the list, because they were
2544 * later in the file
2545 */
2546
2547 /* stable sort the simple nodes */
2548 simple_nodes = g_slist_sort (simple_nodes,
2549 node_compare_func);
2550
2551 prev = NULL;
2552 tmp = simple_nodes;
2553 while (tmp != NULL)
2554 {
2555 GSList *next = tmp->next;
2556
2557 if (prev)
2558 {
2559 MenuLayoutNode *p = prev->data;
2560 MenuLayoutNode *n = tmp->data;
2561
2562 if (node_compare_func (p, n) == 0)
2563 {
2564 /* nuke it! */
2565 menu_layout_node_unlink (n);
2566 simple_nodes = g_slist_delete_link (simple_nodes, tmp);
2567 tmp = prev;
2568 }
2569 }
2570
2571 prev = tmp;
2572 tmp = next;
2573 }
2574
2575 g_slist_free (simple_nodes);
2576 simple_nodes = NULL;
2577
2578 /* stable sort the menu nodes (the sort includes the
2579 * parents of the nodes in the comparison). Remember
2580 * the list is backward.
2581 */
2582 menu_layout_nodes = g_slist_sort (menu_layout_nodes,
2583 node_menu_compare_func);
2584
2585 prev = NULL;
2586 tmp = menu_layout_nodes;
2587 while (tmp != NULL)
2588 {
2589 GSList *next = tmp->next;
2590
2591 if (prev)
2592 {
2593 MenuLayoutNode *p = prev->data;
2594 MenuLayoutNode *n = tmp->data;
2595
2596 if (node_menu_compare_func (p, n) == 0)
2597 {
2598 /* Move children of first menu to the start of second
2599 * menu and nuke the first menu
2600 */
2601 move_children (n, p);
2602 menu_layout_node_unlink (n);
2603 menu_layout_nodes = g_slist_delete_link (menu_layout_nodes, tmp);
2604 tmp = prev;
2605 }
2606 }
2607
2608 prev = tmp;
2609 tmp = next;
2610 }
2611
2612 g_slist_free (menu_layout_nodes);
2613 menu_layout_nodes = NULL;
2614
2615 /* Recursively clean up all children */
2616 child = menu_layout_node_get_children (layout);
2617 while (child != NULL)
2618 {
2619 if (menu_layout_node_get_type (child) == MENU_LAYOUT_NODE_MENU)
2620 gmenu_tree_strip_duplicate_children (tree, child);
2621
2622 child = menu_layout_node_get_next (child);
2623 }
2624 }
2625
2626 static MenuLayoutNode *
find_submenu(MenuLayoutNode * layout,const char * path,gboolean create_if_not_found)2627 find_submenu (MenuLayoutNode *layout,
2628 const char *path,
2629 gboolean create_if_not_found)
2630 {
2631 MenuLayoutNode *child;
2632 const char *slash;
2633 const char *next_path;
2634 char *name;
2635
2636 menu_verbose (" (splitting \"%s\")\n", path);
2637
2638 if (path[0] == '\0' || path[0] == G_DIR_SEPARATOR)
2639 return NULL;
2640
2641 slash = strchr (path, G_DIR_SEPARATOR);
2642 if (slash != NULL)
2643 {
2644 name = g_strndup (path, slash - path);
2645 next_path = slash + 1;
2646 if (*next_path == '\0')
2647 next_path = NULL;
2648 }
2649 else
2650 {
2651 name = g_strdup (path);
2652 next_path = NULL;
2653 }
2654
2655 child = menu_layout_node_get_children (layout);
2656 while (child != NULL)
2657 {
2658 switch (menu_layout_node_get_type (child))
2659 {
2660 case MENU_LAYOUT_NODE_MENU:
2661 {
2662 if (strcmp (name, menu_layout_node_menu_get_name (child)) == 0)
2663 {
2664 menu_verbose ("MenuNode %p found for path component \"%s\"\n",
2665 child, name);
2666
2667 g_free (name);
2668
2669 if (!next_path)
2670 {
2671 menu_verbose (" Found menu node %p parent is %p\n",
2672 child, layout);
2673 return child;
2674 }
2675
2676 return find_submenu (child, next_path, create_if_not_found);
2677 }
2678 }
2679 break;
2680
2681 default:
2682 break;
2683 }
2684
2685 child = menu_layout_node_get_next (child);
2686 }
2687
2688 if (create_if_not_found)
2689 {
2690 MenuLayoutNode *name_node;
2691
2692 child = menu_layout_node_new (MENU_LAYOUT_NODE_MENU);
2693 menu_layout_node_append_child (layout, child);
2694
2695 name_node = menu_layout_node_new (MENU_LAYOUT_NODE_NAME);
2696 menu_layout_node_set_content (name_node, name);
2697 menu_layout_node_append_child (child, name_node);
2698 menu_layout_node_unref (name_node);
2699
2700 menu_verbose (" Created menu node %p parent is %p\n",
2701 child, layout);
2702
2703 menu_layout_node_unref (child);
2704 g_free (name);
2705
2706 if (!next_path)
2707 return child;
2708
2709 return find_submenu (child, next_path, create_if_not_found);
2710 }
2711 else
2712 {
2713 g_free (name);
2714 return NULL;
2715 }
2716 }
2717
2718 /* To call this you first have to strip duplicate children once,
2719 * otherwise when you move a menu Foo to Bar then you may only
2720 * move one of Foo, not all the merged Foo.
2721 */
2722 static void
gmenu_tree_execute_moves(GMenuTree * tree,MenuLayoutNode * layout,gboolean * need_remove_dups_p)2723 gmenu_tree_execute_moves (GMenuTree *tree,
2724 MenuLayoutNode *layout,
2725 gboolean *need_remove_dups_p)
2726 {
2727 MenuLayoutNode *child;
2728 gboolean need_remove_dups;
2729 GSList *move_nodes;
2730 GSList *tmp;
2731
2732 need_remove_dups = FALSE;
2733
2734 move_nodes = NULL;
2735
2736 child = menu_layout_node_get_children (layout);
2737 while (child != NULL)
2738 {
2739 switch (menu_layout_node_get_type (child))
2740 {
2741 case MENU_LAYOUT_NODE_MENU:
2742 /* Recurse - we recurse first and process the current node
2743 * second, as the spec dictates.
2744 */
2745 gmenu_tree_execute_moves (tree, child, &need_remove_dups);
2746 break;
2747
2748 case MENU_LAYOUT_NODE_MOVE:
2749 move_nodes = g_slist_prepend (move_nodes, child);
2750 break;
2751
2752 default:
2753 break;
2754 }
2755
2756 child = menu_layout_node_get_next (child);
2757 }
2758
2759 /* We need to execute the move operations in the order that they appear */
2760 move_nodes = g_slist_reverse (move_nodes);
2761
2762 tmp = move_nodes;
2763 while (tmp != NULL)
2764 {
2765 MenuLayoutNode *move_node = tmp->data;
2766 MenuLayoutNode *old_node;
2767 GSList *next = tmp->next;
2768 const char *old;
2769 const char *new;
2770
2771 old = menu_layout_node_move_get_old (move_node);
2772 new = menu_layout_node_move_get_new (move_node);
2773 g_assert (old != NULL && new != NULL);
2774
2775 menu_verbose ("executing <Move> old = \"%s\" new = \"%s\"\n",
2776 old, new);
2777
2778 old_node = find_submenu (layout, old, FALSE);
2779 if (old_node != NULL)
2780 {
2781 MenuLayoutNode *new_node;
2782
2783 /* here we can create duplicates anywhere below the
2784 * node
2785 */
2786 need_remove_dups = TRUE;
2787
2788 /* look up new node creating it and its parents if
2789 * required
2790 */
2791 new_node = find_submenu (layout, new, TRUE);
2792 g_assert (new_node != NULL);
2793
2794 move_children (old_node, new_node);
2795
2796 menu_layout_node_unlink (old_node);
2797 }
2798
2799 menu_layout_node_unlink (move_node);
2800
2801 tmp = next;
2802 }
2803
2804 g_slist_free (move_nodes);
2805
2806 /* This oddness is to ensure we only remove dups once,
2807 * at the root, instead of recursing the tree over
2808 * and over.
2809 */
2810 if (need_remove_dups_p)
2811 *need_remove_dups_p = need_remove_dups;
2812 else if (need_remove_dups)
2813 gmenu_tree_strip_duplicate_children (tree, layout);
2814 }
2815
2816 static gboolean
gmenu_tree_load_layout(GMenuTree * tree,GError ** error)2817 gmenu_tree_load_layout (GMenuTree *tree,
2818 GError **error)
2819 {
2820 GHashTable *loaded_menu_files;
2821
2822 if (tree->layout)
2823 return TRUE;
2824
2825 if (!gmenu_tree_canonicalize_path (tree, error))
2826 return FALSE;
2827
2828 menu_verbose ("Loading menu layout from \"%s\"\n",
2829 tree->canonical_path);
2830
2831 tree->layout = menu_layout_load (tree->canonical_path,
2832 tree->non_prefixed_basename,
2833 error);
2834 if (!tree->layout)
2835 return FALSE;
2836
2837 loaded_menu_files = g_hash_table_new (g_str_hash, g_str_equal);
2838 g_hash_table_insert (loaded_menu_files, tree->canonical_path, GUINT_TO_POINTER (TRUE));
2839 gmenu_tree_resolve_files (tree, loaded_menu_files, tree->layout);
2840 g_hash_table_destroy (loaded_menu_files);
2841
2842 gmenu_tree_strip_duplicate_children (tree, tree->layout);
2843 gmenu_tree_execute_moves (tree, tree->layout, NULL);
2844
2845 return TRUE;
2846 }
2847
2848 static void
gmenu_tree_force_reload(GMenuTree * tree)2849 gmenu_tree_force_reload (GMenuTree *tree)
2850 {
2851 gmenu_tree_force_rebuild (tree);
2852
2853 if (tree->layout)
2854 menu_layout_node_unref (tree->layout);
2855 tree->layout = NULL;
2856 }
2857
2858 typedef struct
2859 {
2860 DesktopEntrySet *set;
2861 const char *category;
2862 } GetByCategoryForeachData;
2863
2864 static void
get_by_category_foreach(const char * file_id,DesktopEntry * entry,GetByCategoryForeachData * data)2865 get_by_category_foreach (const char *file_id,
2866 DesktopEntry *entry,
2867 GetByCategoryForeachData *data)
2868 {
2869 if (desktop_entry_has_category (entry, data->category))
2870 desktop_entry_set_add_entry (data->set, entry, file_id);
2871 }
2872
2873 static void
get_by_category(DesktopEntrySet * entry_pool,DesktopEntrySet * set,const char * category)2874 get_by_category (DesktopEntrySet *entry_pool,
2875 DesktopEntrySet *set,
2876 const char *category)
2877 {
2878 GetByCategoryForeachData data;
2879
2880 data.set = set;
2881 data.category = category;
2882
2883 desktop_entry_set_foreach (entry_pool,
2884 (DesktopEntrySetForeachFunc) get_by_category_foreach,
2885 &data);
2886 }
2887
2888 static DesktopEntrySet *
process_include_rules(MenuLayoutNode * layout,DesktopEntrySet * entry_pool)2889 process_include_rules (MenuLayoutNode *layout,
2890 DesktopEntrySet *entry_pool)
2891 {
2892 DesktopEntrySet *set = NULL;
2893
2894 switch (menu_layout_node_get_type (layout))
2895 {
2896 case MENU_LAYOUT_NODE_AND:
2897 {
2898 MenuLayoutNode *child;
2899
2900 menu_verbose ("Processing <And>\n");
2901
2902 child = menu_layout_node_get_children (layout);
2903 while (child != NULL)
2904 {
2905 DesktopEntrySet *child_set;
2906
2907 child_set = process_include_rules (child, entry_pool);
2908
2909 if (set == NULL)
2910 {
2911 set = child_set;
2912 }
2913 else
2914 {
2915 desktop_entry_set_intersection (set, child_set);
2916 desktop_entry_set_unref (child_set);
2917 }
2918
2919 /* as soon as we get empty results, we can bail,
2920 * because it's an AND
2921 */
2922 if (desktop_entry_set_get_count (set) == 0)
2923 break;
2924
2925 child = menu_layout_node_get_next (child);
2926 }
2927 menu_verbose ("Processed <And>\n");
2928 }
2929 break;
2930
2931 case MENU_LAYOUT_NODE_OR:
2932 {
2933 MenuLayoutNode *child;
2934
2935 menu_verbose ("Processing <Or>\n");
2936
2937 child = menu_layout_node_get_children (layout);
2938 while (child != NULL)
2939 {
2940 DesktopEntrySet *child_set;
2941
2942 child_set = process_include_rules (child, entry_pool);
2943
2944 if (set == NULL)
2945 {
2946 set = child_set;
2947 }
2948 else
2949 {
2950 desktop_entry_set_union (set, child_set);
2951 desktop_entry_set_unref (child_set);
2952 }
2953
2954 child = menu_layout_node_get_next (child);
2955 }
2956 menu_verbose ("Processed <Or>\n");
2957 }
2958 break;
2959
2960 case MENU_LAYOUT_NODE_NOT:
2961 {
2962 /* First get the OR of all the rules */
2963 MenuLayoutNode *child;
2964
2965 menu_verbose ("Processing <Not>\n");
2966
2967 child = menu_layout_node_get_children (layout);
2968 while (child != NULL)
2969 {
2970 DesktopEntrySet *child_set;
2971
2972 child_set = process_include_rules (child, entry_pool);
2973
2974 if (set == NULL)
2975 {
2976 set = child_set;
2977 }
2978 else
2979 {
2980 desktop_entry_set_union (set, child_set);
2981 desktop_entry_set_unref (child_set);
2982 }
2983
2984 child = menu_layout_node_get_next (child);
2985 }
2986
2987 if (set != NULL)
2988 {
2989 DesktopEntrySet *inverted;
2990
2991 /* Now invert the result */
2992 inverted = desktop_entry_set_new ();
2993 desktop_entry_set_union (inverted, entry_pool);
2994 desktop_entry_set_subtract (inverted, set);
2995 desktop_entry_set_unref (set);
2996 set = inverted;
2997 }
2998 menu_verbose ("Processed <Not>\n");
2999 }
3000 break;
3001
3002 case MENU_LAYOUT_NODE_ALL:
3003 menu_verbose ("Processing <All>\n");
3004 set = desktop_entry_set_new ();
3005 desktop_entry_set_union (set, entry_pool);
3006 menu_verbose ("Processed <All>\n");
3007 break;
3008
3009 case MENU_LAYOUT_NODE_FILENAME:
3010 {
3011 DesktopEntry *entry;
3012
3013 menu_verbose ("Processing <Filename>%s</Filename>\n",
3014 menu_layout_node_get_content (layout));
3015
3016 entry = desktop_entry_set_lookup (entry_pool,
3017 menu_layout_node_get_content (layout));
3018 if (entry != NULL)
3019 {
3020 set = desktop_entry_set_new ();
3021 desktop_entry_set_add_entry (set,
3022 entry,
3023 menu_layout_node_get_content (layout));
3024 }
3025 menu_verbose ("Processed <Filename>%s</Filename>\n",
3026 menu_layout_node_get_content (layout));
3027 }
3028 break;
3029
3030 case MENU_LAYOUT_NODE_CATEGORY:
3031 menu_verbose ("Processing <Category>%s</Category>\n",
3032 menu_layout_node_get_content (layout));
3033 set = desktop_entry_set_new ();
3034 get_by_category (entry_pool, set, menu_layout_node_get_content (layout));
3035 menu_verbose ("Processed <Category>%s</Category>\n",
3036 menu_layout_node_get_content (layout));
3037 break;
3038
3039 default:
3040 break;
3041 }
3042
3043 if (set == NULL)
3044 set = desktop_entry_set_new (); /* create an empty set */
3045
3046 menu_verbose ("Matched %d entries\n", desktop_entry_set_get_count (set));
3047
3048 return set;
3049 }
3050
3051 static void
collect_layout_info(MenuLayoutNode * layout,GSList ** layout_info)3052 collect_layout_info (MenuLayoutNode *layout,
3053 GSList **layout_info)
3054 {
3055 MenuLayoutNode *iter;
3056
3057 g_slist_foreach (*layout_info,
3058 (GFunc) menu_layout_node_unref,
3059 NULL);
3060 g_slist_free (*layout_info);
3061 *layout_info = NULL;
3062
3063 iter = menu_layout_node_get_children (layout);
3064 while (iter != NULL)
3065 {
3066 switch (menu_layout_node_get_type (iter))
3067 {
3068 case MENU_LAYOUT_NODE_MENUNAME:
3069 case MENU_LAYOUT_NODE_FILENAME:
3070 case MENU_LAYOUT_NODE_SEPARATOR:
3071 case MENU_LAYOUT_NODE_MERGE:
3072 *layout_info = g_slist_prepend (*layout_info,
3073 menu_layout_node_ref (iter));
3074 break;
3075
3076 default:
3077 break;
3078 }
3079
3080 iter = menu_layout_node_get_next (iter);
3081 }
3082
3083 *layout_info = g_slist_reverse (*layout_info);
3084 }
3085
3086 static void
entries_listify_foreach(const char * desktop_file_id,DesktopEntry * desktop_entry,GMenuTreeDirectory * directory)3087 entries_listify_foreach (const char *desktop_file_id,
3088 DesktopEntry *desktop_entry,
3089 GMenuTreeDirectory *directory)
3090 {
3091 directory->entries =
3092 g_slist_prepend (directory->entries,
3093 gmenu_tree_entry_new (directory,
3094 desktop_entry,
3095 desktop_file_id,
3096 FALSE,
3097 FALSE));
3098 }
3099
3100 static void
excluded_entries_listify_foreach(const char * desktop_file_id,DesktopEntry * desktop_entry,GMenuTreeDirectory * directory)3101 excluded_entries_listify_foreach (const char *desktop_file_id,
3102 DesktopEntry *desktop_entry,
3103 GMenuTreeDirectory *directory)
3104 {
3105 directory->entries =
3106 g_slist_prepend (directory->entries,
3107 gmenu_tree_entry_new (directory,
3108 desktop_entry,
3109 desktop_file_id,
3110 TRUE,
3111 FALSE));
3112 }
3113
3114 static void
unallocated_entries_listify_foreach(const char * desktop_file_id,DesktopEntry * desktop_entry,GMenuTreeDirectory * directory)3115 unallocated_entries_listify_foreach (const char *desktop_file_id,
3116 DesktopEntry *desktop_entry,
3117 GMenuTreeDirectory *directory)
3118 {
3119 directory->entries =
3120 g_slist_prepend (directory->entries,
3121 gmenu_tree_entry_new (directory,
3122 desktop_entry,
3123 desktop_file_id,
3124 FALSE,
3125 TRUE));
3126 }
3127
3128 static void
set_default_layout_values(GMenuTreeDirectory * parent,GMenuTreeDirectory * child)3129 set_default_layout_values (GMenuTreeDirectory *parent,
3130 GMenuTreeDirectory *child)
3131 {
3132 GSList *tmp;
3133
3134 /* if the child has a defined default layout, we don't want to override its
3135 * values. The parent might have a non-defined layout info (ie, no child of
3136 * the DefaultLayout node) but it doesn't meant the default layout values
3137 * (ie, DefaultLayout attributes) aren't different from the global defaults.
3138 */
3139 if (child->default_layout_info != NULL ||
3140 child->default_layout_values.mask != MENU_LAYOUT_VALUES_NONE)
3141 return;
3142
3143 child->default_layout_values = parent->default_layout_values;
3144
3145 tmp = child->subdirs;
3146 while (tmp != NULL)
3147 {
3148 GMenuTreeDirectory *subdir = tmp->data;
3149
3150 set_default_layout_values (child, subdir);
3151
3152 tmp = tmp->next;
3153 }
3154 }
3155
3156 static GMenuTreeDirectory *
process_layout(GMenuTree * tree,GMenuTreeDirectory * parent,MenuLayoutNode * layout,DesktopEntrySet * allocated)3157 process_layout (GMenuTree *tree,
3158 GMenuTreeDirectory *parent,
3159 MenuLayoutNode *layout,
3160 DesktopEntrySet *allocated)
3161 {
3162 MenuLayoutNode *layout_iter;
3163 GMenuTreeDirectory *directory;
3164 DesktopEntrySet *entry_pool;
3165 DesktopEntrySet *entries;
3166 DesktopEntrySet *allocated_set;
3167 DesktopEntrySet *excluded_set;
3168 gboolean deleted;
3169 gboolean only_unallocated;
3170 GSList *tmp;
3171
3172 g_assert (menu_layout_node_get_type (layout) == MENU_LAYOUT_NODE_MENU);
3173 g_assert (menu_layout_node_menu_get_name (layout) != NULL);
3174
3175 directory = gmenu_tree_directory_new (tree, parent,
3176 menu_layout_node_menu_get_name (layout));
3177
3178 menu_verbose ("=== Menu name = %s ===\n", directory->name);
3179
3180
3181 deleted = FALSE;
3182 only_unallocated = FALSE;
3183
3184 entries = desktop_entry_set_new ();
3185 allocated_set = desktop_entry_set_new ();
3186
3187 if (tree->flags & GMENU_TREE_FLAGS_INCLUDE_EXCLUDED)
3188 excluded_set = desktop_entry_set_new ();
3189 else
3190 excluded_set = NULL;
3191
3192 entry_pool = _entry_directory_list_get_all_desktops (menu_layout_node_menu_get_app_dirs (layout));
3193
3194 layout_iter = menu_layout_node_get_children (layout);
3195 while (layout_iter != NULL)
3196 {
3197 switch (menu_layout_node_get_type (layout_iter))
3198 {
3199 case MENU_LAYOUT_NODE_MENU:
3200 /* recurse */
3201 {
3202 GMenuTreeDirectory *child_dir;
3203
3204 menu_verbose ("Processing <Menu>\n");
3205
3206 child_dir = process_layout (tree,
3207 directory,
3208 layout_iter,
3209 allocated);
3210 if (child_dir)
3211 directory->subdirs = g_slist_prepend (directory->subdirs,
3212 child_dir);
3213
3214 menu_verbose ("Processed <Menu>\n");
3215 }
3216 break;
3217
3218 case MENU_LAYOUT_NODE_INCLUDE:
3219 {
3220 /* The match rule children of the <Include> are
3221 * independent (logical OR) so we can process each one by
3222 * itself
3223 */
3224 MenuLayoutNode *rule;
3225
3226 menu_verbose ("Processing <Include> (%d entries)\n",
3227 desktop_entry_set_get_count (entries));
3228
3229 rule = menu_layout_node_get_children (layout_iter);
3230 while (rule != NULL)
3231 {
3232 DesktopEntrySet *rule_set;
3233
3234 rule_set = process_include_rules (rule, entry_pool);
3235 if (rule_set != NULL)
3236 {
3237 desktop_entry_set_union (entries, rule_set);
3238 desktop_entry_set_union (allocated_set, rule_set);
3239 if (excluded_set != NULL)
3240 desktop_entry_set_subtract (excluded_set, rule_set);
3241 desktop_entry_set_unref (rule_set);
3242 }
3243
3244 rule = menu_layout_node_get_next (rule);
3245 }
3246
3247 menu_verbose ("Processed <Include> (%d entries)\n",
3248 desktop_entry_set_get_count (entries));
3249 }
3250 break;
3251
3252 case MENU_LAYOUT_NODE_EXCLUDE:
3253 {
3254 /* The match rule children of the <Exclude> are
3255 * independent (logical OR) so we can process each one by
3256 * itself
3257 */
3258 MenuLayoutNode *rule;
3259
3260 menu_verbose ("Processing <Exclude> (%d entries)\n",
3261 desktop_entry_set_get_count (entries));
3262
3263 rule = menu_layout_node_get_children (layout_iter);
3264 while (rule != NULL)
3265 {
3266 DesktopEntrySet *rule_set;
3267
3268 rule_set = process_include_rules (rule, entry_pool);
3269 if (rule_set != NULL)
3270 {
3271 if (excluded_set != NULL)
3272 desktop_entry_set_union (excluded_set, rule_set);
3273 desktop_entry_set_subtract (entries, rule_set);
3274 desktop_entry_set_unref (rule_set);
3275 }
3276
3277 rule = menu_layout_node_get_next (rule);
3278 }
3279
3280 menu_verbose ("Processed <Exclude> (%d entries)\n",
3281 desktop_entry_set_get_count (entries));
3282 }
3283 break;
3284
3285 case MENU_LAYOUT_NODE_DIRECTORY:
3286 {
3287 DesktopEntry *entry;
3288
3289 menu_verbose ("Processing <Directory>%s</Directory>\n",
3290 menu_layout_node_get_content (layout_iter));
3291
3292 /*
3293 * The last <Directory> to exist wins, so we always try overwriting
3294 */
3295 entry = entry_directory_list_get_directory (menu_layout_node_menu_get_directory_dirs (layout),
3296 menu_layout_node_get_content (layout_iter));
3297
3298 if (entry != NULL)
3299 {
3300 if (!desktop_entry_get_hidden (entry))
3301 {
3302 if (directory->directory_entry)
3303 desktop_entry_unref (directory->directory_entry);
3304 directory->directory_entry = entry; /* pass ref ownership */
3305 }
3306 else
3307 {
3308 desktop_entry_unref (entry);
3309 }
3310 }
3311
3312 menu_verbose ("Processed <Directory> new directory entry = %p (%s)\n",
3313 directory->directory_entry,
3314 directory->directory_entry? desktop_entry_get_path (directory->directory_entry) : "null");
3315 }
3316 break;
3317
3318 case MENU_LAYOUT_NODE_DELETED:
3319 menu_verbose ("Processed <Deleted/>\n");
3320 deleted = TRUE;
3321 break;
3322
3323 case MENU_LAYOUT_NODE_NOT_DELETED:
3324 menu_verbose ("Processed <NotDeleted/>\n");
3325 deleted = FALSE;
3326 break;
3327
3328 case MENU_LAYOUT_NODE_ONLY_UNALLOCATED:
3329 menu_verbose ("Processed <OnlyUnallocated/>\n");
3330 only_unallocated = TRUE;
3331 break;
3332
3333 case MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED:
3334 menu_verbose ("Processed <NotOnlyUnallocated/>\n");
3335 only_unallocated = FALSE;
3336 break;
3337
3338 case MENU_LAYOUT_NODE_DEFAULT_LAYOUT:
3339 menu_layout_node_default_layout_get_values (layout_iter,
3340 &directory->default_layout_values);
3341 collect_layout_info (layout_iter, &directory->default_layout_info);
3342 menu_verbose ("Processed <DefaultLayout/>\n");
3343 break;
3344
3345 case MENU_LAYOUT_NODE_LAYOUT:
3346 collect_layout_info (layout_iter, &directory->layout_info);
3347 menu_verbose ("Processed <Layout/>\n");
3348 break;
3349
3350 default:
3351 break;
3352 }
3353
3354 layout_iter = menu_layout_node_get_next (layout_iter);
3355 }
3356
3357 desktop_entry_set_unref (entry_pool);
3358
3359 directory->only_unallocated = only_unallocated;
3360
3361 if (!directory->only_unallocated)
3362 desktop_entry_set_union (allocated, allocated_set);
3363
3364 desktop_entry_set_unref (allocated_set);
3365
3366 if (directory->directory_entry)
3367 {
3368 if (desktop_entry_get_no_display (directory->directory_entry))
3369 {
3370 directory->is_nodisplay = TRUE;
3371
3372 if (!(tree->flags & GMENU_TREE_FLAGS_INCLUDE_NODISPLAY))
3373 {
3374 menu_verbose ("Not showing menu %s because NoDisplay=true\n",
3375 desktop_entry_get_name (directory->directory_entry));
3376 deleted = TRUE;
3377 }
3378 }
3379
3380 if (!desktop_entry_get_show_in (directory->directory_entry))
3381 {
3382 menu_verbose ("Not showing menu %s because OnlyShowIn!=$DESKTOP or NotShowIn=$DESKTOP (with $DESKTOP=${XDG_CURRENT_DESKTOP:-GNOME})\n",
3383 desktop_entry_get_name (directory->directory_entry));
3384 deleted = TRUE;
3385 }
3386 }
3387
3388 if (deleted)
3389 {
3390 if (excluded_set != NULL)
3391 desktop_entry_set_unref (excluded_set);
3392 desktop_entry_set_unref (entries);
3393 gmenu_tree_item_unref (directory);
3394 return NULL;
3395 }
3396
3397 desktop_entry_set_foreach (entries,
3398 (DesktopEntrySetForeachFunc) entries_listify_foreach,
3399 directory);
3400 desktop_entry_set_unref (entries);
3401
3402 if (excluded_set != NULL)
3403 {
3404 desktop_entry_set_foreach (excluded_set,
3405 (DesktopEntrySetForeachFunc) excluded_entries_listify_foreach,
3406 directory);
3407 desktop_entry_set_unref (excluded_set);
3408 }
3409
3410 tmp = directory->subdirs;
3411 while (tmp != NULL)
3412 {
3413 GMenuTreeDirectory *subdir = tmp->data;
3414
3415 set_default_layout_values (directory, subdir);
3416
3417 tmp = tmp->next;
3418 }
3419
3420 tmp = directory->entries;
3421 while (tmp != NULL)
3422 {
3423 GMenuTreeEntry *entry = tmp->data;
3424 GSList *next = tmp->next;
3425 gboolean delete = FALSE;
3426
3427 /* If adding a new condition to delete here, it has to be added to
3428 * get_still_unallocated_foreach() too */
3429
3430 if (desktop_entry_get_hidden (entry->desktop_entry))
3431 {
3432 menu_verbose ("Deleting %s because Hidden=true\n",
3433 desktop_entry_get_name (entry->desktop_entry));
3434 delete = TRUE;
3435 }
3436
3437 if (!(tree->flags & GMENU_TREE_FLAGS_INCLUDE_NODISPLAY) &&
3438 desktop_entry_get_no_display (entry->desktop_entry))
3439 {
3440 menu_verbose ("Deleting %s because NoDisplay=true\n",
3441 desktop_entry_get_name (entry->desktop_entry));
3442 delete = TRUE;
3443 }
3444
3445 if (!desktop_entry_get_show_in (entry->desktop_entry))
3446 {
3447 menu_verbose ("Deleting %s because OnlyShowIn!=$DESKTOP or NotShowIn=$DESKTOP (with $DESKTOP=${XDG_CURRENT_DESKTOP:-GNOME})\n",
3448 desktop_entry_get_name (entry->desktop_entry));
3449 delete = TRUE;
3450 }
3451
3452 /* No need to filter out based on TryExec since GDesktopAppInfo cannot
3453 * deal with .desktop files with a failed TryExec. */
3454
3455 if (delete)
3456 {
3457 directory->entries = g_slist_delete_link (directory->entries,
3458 tmp);
3459 gmenu_tree_item_unref_and_unset_parent (entry);
3460 }
3461
3462 tmp = next;
3463 }
3464
3465 g_assert (directory->name != NULL);
3466
3467 return directory;
3468 }
3469
3470 static void
process_only_unallocated(GMenuTree * tree,GMenuTreeDirectory * directory,DesktopEntrySet * allocated,DesktopEntrySet * unallocated_used)3471 process_only_unallocated (GMenuTree *tree,
3472 GMenuTreeDirectory *directory,
3473 DesktopEntrySet *allocated,
3474 DesktopEntrySet *unallocated_used)
3475 {
3476 GSList *tmp;
3477
3478 /* For any directory marked only_unallocated, we have to remove any
3479 * entries that were in fact allocated.
3480 */
3481
3482 if (directory->only_unallocated)
3483 {
3484 tmp = directory->entries;
3485 while (tmp != NULL)
3486 {
3487 GMenuTreeEntry *entry = tmp->data;
3488 GSList *next = tmp->next;
3489
3490 if (desktop_entry_set_lookup (allocated, entry->desktop_file_id))
3491 {
3492 directory->entries = g_slist_delete_link (directory->entries,
3493 tmp);
3494 gmenu_tree_item_unref_and_unset_parent (entry);
3495 }
3496 else
3497 {
3498 desktop_entry_set_add_entry (unallocated_used, entry->desktop_entry, entry->desktop_file_id);
3499 }
3500
3501 tmp = next;
3502 }
3503 }
3504
3505 tmp = directory->subdirs;
3506 while (tmp != NULL)
3507 {
3508 GMenuTreeDirectory *subdir = tmp->data;
3509
3510 process_only_unallocated (tree, subdir, allocated, unallocated_used);
3511
3512 tmp = tmp->next;
3513 }
3514 }
3515
3516 typedef struct
3517 {
3518 GMenuTree *tree;
3519 DesktopEntrySet *allocated;
3520 DesktopEntrySet *unallocated_used;
3521 DesktopEntrySet *still_unallocated;
3522 } GetStillUnallocatedForeachData;
3523
3524 static void
get_still_unallocated_foreach(const char * file_id,DesktopEntry * entry,GetStillUnallocatedForeachData * data)3525 get_still_unallocated_foreach (const char *file_id,
3526 DesktopEntry *entry,
3527 GetStillUnallocatedForeachData *data)
3528 {
3529 if (desktop_entry_set_lookup (data->allocated, file_id))
3530 return;
3531
3532 if (desktop_entry_set_lookup (data->unallocated_used, file_id))
3533 return;
3534
3535 /* Same rules than at the end of process_layout() */
3536 if (desktop_entry_get_hidden (entry))
3537 return;
3538
3539 if (!(data->tree->flags & GMENU_TREE_FLAGS_INCLUDE_NODISPLAY) &&
3540 desktop_entry_get_no_display (entry))
3541 return;
3542
3543 if (!desktop_entry_get_show_in (entry))
3544 return;
3545
3546 desktop_entry_set_add_entry (data->still_unallocated, entry, file_id);
3547 }
3548
3549 static void preprocess_layout_info (GMenuTree *tree,
3550 GMenuTreeDirectory *directory);
3551
3552 static GSList *
get_layout_info(GMenuTreeDirectory * directory,gboolean * is_default_layout)3553 get_layout_info (GMenuTreeDirectory *directory,
3554 gboolean *is_default_layout)
3555 {
3556 GMenuTreeDirectory *iter;
3557
3558 if (directory->layout_info != NULL)
3559 {
3560 if (is_default_layout)
3561 {
3562 *is_default_layout = FALSE;
3563 }
3564 return directory->layout_info;
3565 }
3566
3567 /* Even if there's no layout information at all, the result will be an
3568 * implicit default layout */
3569 if (is_default_layout)
3570 {
3571 *is_default_layout = TRUE;
3572 }
3573
3574 iter = directory;
3575 while (iter != NULL)
3576 {
3577 /* FIXME: this is broken: we might skip real parent in the
3578 * XML structure, that are hidden because of inlining. */
3579 if (iter->default_layout_info != NULL)
3580 {
3581 return iter->default_layout_info;
3582 }
3583
3584 iter = GMENU_TREE_ITEM (iter)->parent;
3585 }
3586
3587 return NULL;
3588 }
3589
3590 static void
get_values_with_defaults(MenuLayoutNode * node,MenuLayoutValues * layout_values,MenuLayoutValues * default_layout_values)3591 get_values_with_defaults (MenuLayoutNode *node,
3592 MenuLayoutValues *layout_values,
3593 MenuLayoutValues *default_layout_values)
3594 {
3595 menu_layout_node_menuname_get_values (node, layout_values);
3596
3597 if (!(layout_values->mask & MENU_LAYOUT_VALUES_SHOW_EMPTY))
3598 layout_values->show_empty = default_layout_values->show_empty;
3599
3600 if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_MENUS))
3601 layout_values->inline_menus = default_layout_values->inline_menus;
3602
3603 if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_LIMIT))
3604 layout_values->inline_limit = default_layout_values->inline_limit;
3605
3606 if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_HEADER))
3607 layout_values->inline_header = default_layout_values->inline_header;
3608
3609 if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_ALIAS))
3610 layout_values->inline_alias = default_layout_values->inline_alias;
3611 }
3612
3613 static guint
get_real_subdirs_len(GMenuTreeDirectory * directory)3614 get_real_subdirs_len (GMenuTreeDirectory *directory)
3615 {
3616 guint len;
3617 GSList *tmp;
3618
3619 len = 0;
3620
3621 tmp = directory->subdirs;
3622 while (tmp != NULL)
3623 {
3624 GMenuTreeDirectory *subdir = tmp->data;
3625
3626 tmp = tmp->next;
3627
3628 if (subdir->will_inline_header != G_MAXUINT16)
3629 {
3630 len += get_real_subdirs_len (subdir) + g_slist_length (subdir->entries) + 1;
3631 }
3632 else
3633 len += 1;
3634 }
3635
3636 return len;
3637 }
3638
3639 static void
preprocess_layout_info_subdir_helper(GMenuTree * tree,GMenuTreeDirectory * directory,GMenuTreeDirectory * subdir,MenuLayoutValues * layout_values,gboolean * contents_added,gboolean * should_remove)3640 preprocess_layout_info_subdir_helper (GMenuTree *tree,
3641 GMenuTreeDirectory *directory,
3642 GMenuTreeDirectory *subdir,
3643 MenuLayoutValues *layout_values,
3644 gboolean *contents_added,
3645 gboolean *should_remove)
3646 {
3647 preprocess_layout_info (tree, subdir);
3648
3649 *should_remove = FALSE;
3650 *contents_added = FALSE;
3651
3652 if (subdir->subdirs == NULL && subdir->entries == NULL)
3653 {
3654 if (!(tree->flags & GMENU_TREE_FLAGS_SHOW_EMPTY) &&
3655 !layout_values->show_empty)
3656 {
3657 menu_verbose ("Not showing empty menu '%s'\n", subdir->name);
3658 *should_remove = TRUE;
3659 }
3660 }
3661
3662 else if (layout_values->inline_menus)
3663 {
3664 guint real_subdirs_len;
3665
3666 real_subdirs_len = get_real_subdirs_len (subdir);
3667
3668 if (layout_values->inline_alias &&
3669 real_subdirs_len + g_slist_length (subdir->entries) == 1)
3670 {
3671 GMenuTreeAlias *alias;
3672 GMenuTreeItem *item;
3673 GSList *list;
3674
3675 if (subdir->subdirs != NULL)
3676 list = subdir->subdirs;
3677 else
3678 list = subdir->entries;
3679
3680 item = GMENU_TREE_ITEM (list->data);
3681
3682 menu_verbose ("Inline aliasing '%s' to '%s'\n",
3683 item->type == GMENU_TREE_ITEM_ENTRY ?
3684 g_app_info_get_name (G_APP_INFO (gmenu_tree_entry_get_app_info (GMENU_TREE_ENTRY (item)))) :
3685 (item->type == GMENU_TREE_ITEM_DIRECTORY ?
3686 gmenu_tree_directory_get_name (GMENU_TREE_DIRECTORY (item)) :
3687 gmenu_tree_directory_get_name (GMENU_TREE_ALIAS (item)->directory)),
3688 subdir->name);
3689
3690 alias = gmenu_tree_alias_new (directory, subdir, item);
3691
3692 g_slist_foreach (list,
3693 (GFunc) gmenu_tree_item_unref_and_unset_parent,
3694 NULL);
3695 g_slist_free (list);
3696 subdir->subdirs = NULL;
3697 subdir->entries = NULL;
3698
3699 if (item->type == GMENU_TREE_ITEM_DIRECTORY)
3700 directory->subdirs = g_slist_append (directory->subdirs, alias);
3701 else
3702 directory->entries = g_slist_append (directory->entries, alias);
3703
3704 *contents_added = TRUE;
3705 *should_remove = TRUE;
3706 }
3707
3708 else if (layout_values->inline_limit == 0 ||
3709 layout_values->inline_limit >= real_subdirs_len + g_slist_length (subdir->entries))
3710 {
3711 if (layout_values->inline_header)
3712 {
3713 menu_verbose ("Creating inline header with name '%s'\n", subdir->name);
3714 /* we're limited to 16-bits to spare some memory; if the limit is
3715 * higher than that (would be crazy), we just consider it's
3716 * unlimited */
3717 if (layout_values->inline_limit < G_MAXUINT16)
3718 subdir->will_inline_header = layout_values->inline_limit;
3719 else
3720 subdir->will_inline_header = 0;
3721 }
3722 else
3723 {
3724 g_slist_foreach (subdir->subdirs,
3725 (GFunc) gmenu_tree_item_set_parent,
3726 directory);
3727 directory->subdirs = g_slist_concat (directory->subdirs,
3728 subdir->subdirs);
3729 subdir->subdirs = NULL;
3730
3731 g_slist_foreach (subdir->entries,
3732 (GFunc) gmenu_tree_item_set_parent,
3733 directory);
3734 directory->entries = g_slist_concat (directory->entries,
3735 subdir->entries);
3736 subdir->entries = NULL;
3737
3738 *contents_added = TRUE;
3739 *should_remove = TRUE;
3740 }
3741
3742 menu_verbose ("Inlining directory contents of '%s' to '%s'\n",
3743 subdir->name, directory->name);
3744 }
3745 }
3746 }
3747
3748 static void
preprocess_layout_info(GMenuTree * tree,GMenuTreeDirectory * directory)3749 preprocess_layout_info (GMenuTree *tree,
3750 GMenuTreeDirectory *directory)
3751 {
3752 GSList *tmp;
3753 GSList *layout_info;
3754 gboolean using_default_layout;
3755 GSList *last_subdir;
3756 gboolean strip_duplicates;
3757 gboolean contents_added;
3758 gboolean should_remove;
3759 GSList *subdirs_sentinel;
3760
3761 /* Note: we need to preprocess all menus, even if the layout mask for a menu
3762 * is MENU_LAYOUT_VALUES_NONE: in this case, we need to remove empty menus;
3763 * and the layout mask can be different for a submenu anyway */
3764
3765 menu_verbose ("Processing menu layout inline hints for %s\n", directory->name);
3766 g_assert (!directory->preprocessed);
3767
3768 strip_duplicates = FALSE;
3769 /* we use last_subdir to track the last non-inlined subdirectory */
3770 last_subdir = g_slist_last (directory->subdirs);
3771
3772 /*
3773 * First process subdirectories with explicit layout
3774 */
3775 layout_info = get_layout_info (directory, &using_default_layout);
3776 tmp = layout_info;
3777 /* see comment below about Menuname to understand why we leave the loop if
3778 * last_subdir is NULL */
3779 while (tmp != NULL && last_subdir != NULL)
3780 {
3781 MenuLayoutNode *node = tmp->data;
3782 MenuLayoutValues layout_values;
3783 const char *name;
3784 GMenuTreeDirectory *subdir;
3785 GSList *subdir_l;
3786
3787 tmp = tmp->next;
3788
3789 /* only Menuname nodes are relevant here */
3790 if (menu_layout_node_get_type (node) != MENU_LAYOUT_NODE_MENUNAME)
3791 continue;
3792
3793 get_values_with_defaults (node,
3794 &layout_values,
3795 &directory->default_layout_values);
3796
3797 /* find the subdirectory that is affected by those attributes */
3798 name = menu_layout_node_get_content (node);
3799 subdir = NULL;
3800 subdir_l = directory->subdirs;
3801 while (subdir_l != NULL)
3802 {
3803 subdir = subdir_l->data;
3804
3805 if (!strcmp (subdir->name, name))
3806 break;
3807
3808 subdir = NULL;
3809 subdir_l = subdir_l->next;
3810
3811 /* We do not want to use Menuname on a menu that appeared via
3812 * inlining: without inlining, the Menuname wouldn't have matched
3813 * anything, and we want to keep the same behavior.
3814 * Unless the layout is a default layout, in which case the Menuname
3815 * does match the subdirectory. */
3816 if (!using_default_layout && subdir_l == last_subdir)
3817 {
3818 subdir_l = NULL;
3819 break;
3820 }
3821 }
3822
3823 if (subdir == NULL)
3824 continue;
3825
3826 preprocess_layout_info_subdir_helper (tree, directory,
3827 subdir, &layout_values,
3828 &contents_added, &should_remove);
3829 strip_duplicates = strip_duplicates || contents_added;
3830 if (should_remove)
3831 {
3832 if (last_subdir == subdir_l)
3833 {
3834 /* we need to recompute last_subdir since we'll remove it from
3835 * the list */
3836 GSList *buf;
3837
3838 if (subdir_l == directory->subdirs)
3839 last_subdir = NULL;
3840 else
3841 {
3842 buf = directory->subdirs;
3843 while (buf != NULL && buf->next != subdir_l)
3844 buf = buf->next;
3845 last_subdir = buf;
3846 }
3847 }
3848
3849 directory->subdirs = g_slist_remove (directory->subdirs, subdir);
3850 gmenu_tree_item_unref_and_unset_parent (GMENU_TREE_ITEM (subdir));
3851 }
3852 }
3853
3854 /*
3855 * Now process the subdirectories with no explicit layout
3856 */
3857 /* this is bogus data, but we just need the pointer anyway */
3858 subdirs_sentinel = g_slist_prepend (directory->subdirs, PACKAGE);
3859 directory->subdirs = subdirs_sentinel;
3860
3861 tmp = directory->subdirs;
3862 while (tmp->next != NULL)
3863 {
3864 GMenuTreeDirectory *subdir = tmp->next->data;
3865
3866 if (subdir->preprocessed)
3867 {
3868 tmp = tmp->next;
3869 continue;
3870 }
3871
3872 preprocess_layout_info_subdir_helper (tree, directory,
3873 subdir, &directory->default_layout_values,
3874 &contents_added, &should_remove);
3875 strip_duplicates = strip_duplicates || contents_added;
3876 if (should_remove)
3877 {
3878 tmp = g_slist_delete_link (tmp, tmp->next);
3879 gmenu_tree_item_unref_and_unset_parent (GMENU_TREE_ITEM (subdir));
3880 }
3881 else
3882 tmp = tmp->next;
3883 }
3884
3885 /* remove the sentinel */
3886 directory->subdirs = g_slist_delete_link (directory->subdirs,
3887 directory->subdirs);
3888
3889 /*
3890 * Finally, remove duplicates if needed
3891 */
3892 if (strip_duplicates)
3893 {
3894 /* strip duplicate entries; there should be no duplicate directories */
3895 directory->entries = g_slist_sort (directory->entries,
3896 (GCompareFunc) gmenu_tree_entry_compare_by_id);
3897 tmp = directory->entries;
3898 while (tmp != NULL && tmp->next != NULL)
3899 {
3900 GMenuTreeItem *a = tmp->data;
3901 GMenuTreeItem *b = tmp->next->data;
3902
3903 if (a->type == GMENU_TREE_ITEM_ALIAS)
3904 a = GMENU_TREE_ALIAS (a)->aliased_item;
3905
3906 if (b->type == GMENU_TREE_ITEM_ALIAS)
3907 b = GMENU_TREE_ALIAS (b)->aliased_item;
3908
3909 if (strcmp (GMENU_TREE_ENTRY (a)->desktop_file_id,
3910 GMENU_TREE_ENTRY (b)->desktop_file_id) == 0)
3911 {
3912 tmp = g_slist_delete_link (tmp, tmp->next);
3913 gmenu_tree_item_unref (b);
3914 }
3915 else
3916 tmp = tmp->next;
3917 }
3918 }
3919
3920 directory->preprocessed = TRUE;
3921 }
3922
3923 static void process_layout_info (GMenuTree *tree,
3924 GMenuTreeDirectory *directory);
3925
3926 static void
check_pending_separator(GMenuTreeDirectory * directory)3927 check_pending_separator (GMenuTreeDirectory *directory)
3928 {
3929 if (directory->layout_pending_separator)
3930 {
3931 menu_verbose ("Adding pending separator in '%s'\n", directory->name);
3932
3933 directory->contents = g_slist_append (directory->contents,
3934 gmenu_tree_separator_new (directory));
3935 directory->layout_pending_separator = FALSE;
3936 }
3937 }
3938
3939 static void
merge_alias(GMenuTree * tree,GMenuTreeDirectory * directory,GMenuTreeAlias * alias)3940 merge_alias (GMenuTree *tree,
3941 GMenuTreeDirectory *directory,
3942 GMenuTreeAlias *alias)
3943 {
3944 menu_verbose ("Merging alias '%s' in directory '%s'\n",
3945 alias->directory->name, directory->name);
3946
3947 if (alias->aliased_item->type == GMENU_TREE_ITEM_DIRECTORY)
3948 {
3949 process_layout_info (tree, GMENU_TREE_DIRECTORY (alias->aliased_item));
3950 }
3951
3952 check_pending_separator (directory);
3953
3954 directory->contents = g_slist_append (directory->contents,
3955 gmenu_tree_item_ref (alias));
3956 }
3957
3958 static void
merge_subdir(GMenuTree * tree,GMenuTreeDirectory * directory,GMenuTreeDirectory * subdir)3959 merge_subdir (GMenuTree *tree,
3960 GMenuTreeDirectory *directory,
3961 GMenuTreeDirectory *subdir)
3962 {
3963 menu_verbose ("Merging subdir '%s' in directory '%s'\n",
3964 subdir->name, directory->name);
3965
3966 process_layout_info (tree, subdir);
3967
3968 check_pending_separator (directory);
3969
3970 if (subdir->will_inline_header == 0 ||
3971 (subdir->will_inline_header != G_MAXUINT16 &&
3972 g_slist_length (subdir->contents) <= subdir->will_inline_header))
3973 {
3974 GMenuTreeHeader *header;
3975
3976 header = gmenu_tree_header_new (directory, subdir);
3977 directory->contents = g_slist_append (directory->contents, header);
3978
3979 g_slist_foreach (subdir->contents,
3980 (GFunc) gmenu_tree_item_set_parent,
3981 directory);
3982 directory->contents = g_slist_concat (directory->contents,
3983 subdir->contents);
3984 subdir->contents = NULL;
3985 subdir->will_inline_header = G_MAXUINT16;
3986
3987 gmenu_tree_item_set_parent (GMENU_TREE_ITEM (subdir), NULL);
3988 }
3989 else
3990 {
3991 directory->contents = g_slist_append (directory->contents,
3992 gmenu_tree_item_ref (subdir));
3993 }
3994 }
3995
3996 static void
merge_subdir_by_name(GMenuTree * tree,GMenuTreeDirectory * directory,const char * subdir_name)3997 merge_subdir_by_name (GMenuTree *tree,
3998 GMenuTreeDirectory *directory,
3999 const char *subdir_name)
4000 {
4001 GSList *tmp;
4002
4003 menu_verbose ("Attempting to merge subdir '%s' in directory '%s'\n",
4004 subdir_name, directory->name);
4005
4006 tmp = directory->subdirs;
4007 while (tmp != NULL)
4008 {
4009 GMenuTreeDirectory *subdir = tmp->data;
4010 GSList *next = tmp->next;
4011
4012 /* if it's an alias, then it cannot be affected by
4013 * the Merge nodes in the layout */
4014 if (GMENU_TREE_ITEM (subdir)->type == GMENU_TREE_ITEM_ALIAS)
4015 continue;
4016
4017 if (!strcmp (subdir->name, subdir_name))
4018 {
4019 directory->subdirs = g_slist_delete_link (directory->subdirs, tmp);
4020 merge_subdir (tree, directory, subdir);
4021 gmenu_tree_item_unref (subdir);
4022 }
4023
4024 tmp = next;
4025 }
4026 }
4027
4028 static void
merge_entry(GMenuTree * tree,GMenuTreeDirectory * directory,GMenuTreeEntry * entry)4029 merge_entry (GMenuTree *tree,
4030 GMenuTreeDirectory *directory,
4031 GMenuTreeEntry *entry)
4032 {
4033 menu_verbose ("Merging entry '%s' in directory '%s'\n",
4034 entry->desktop_file_id, directory->name);
4035
4036 check_pending_separator (directory);
4037 directory->contents = g_slist_append (directory->contents,
4038 gmenu_tree_item_ref (entry));
4039 }
4040
4041 static void
merge_entry_by_id(GMenuTree * tree,GMenuTreeDirectory * directory,const char * file_id)4042 merge_entry_by_id (GMenuTree *tree,
4043 GMenuTreeDirectory *directory,
4044 const char *file_id)
4045 {
4046 GSList *tmp;
4047
4048 menu_verbose ("Attempting to merge entry '%s' in directory '%s'\n",
4049 file_id, directory->name);
4050
4051 tmp = directory->entries;
4052 while (tmp != NULL)
4053 {
4054 GMenuTreeEntry *entry = tmp->data;
4055 GSList *next = tmp->next;
4056
4057 /* if it's an alias, then it cannot be affected by
4058 * the Merge nodes in the layout */
4059 if (GMENU_TREE_ITEM (entry)->type == GMENU_TREE_ITEM_ALIAS)
4060 continue;
4061
4062 if (!strcmp (entry->desktop_file_id, file_id))
4063 {
4064 directory->entries = g_slist_delete_link (directory->entries, tmp);
4065 merge_entry (tree, directory, entry);
4066 gmenu_tree_item_unref (entry);
4067 }
4068
4069 tmp = next;
4070 }
4071 }
4072
4073 static inline gboolean
find_name_in_list(const char * name,GSList * list)4074 find_name_in_list (const char *name,
4075 GSList *list)
4076 {
4077 while (list != NULL)
4078 {
4079 if (!strcmp (name, list->data))
4080 return TRUE;
4081
4082 list = list->next;
4083 }
4084
4085 return FALSE;
4086 }
4087
4088 static void
merge_subdirs(GMenuTree * tree,GMenuTreeDirectory * directory,GSList * except)4089 merge_subdirs (GMenuTree *tree,
4090 GMenuTreeDirectory *directory,
4091 GSList *except)
4092 {
4093 GSList *subdirs;
4094 GSList *tmp;
4095
4096 menu_verbose ("Merging subdirs in directory '%s'\n", directory->name);
4097
4098 subdirs = directory->subdirs;
4099 directory->subdirs = NULL;
4100
4101 subdirs = g_slist_sort_with_data (subdirs,
4102 (GCompareDataFunc) gmenu_tree_item_compare,
4103 GINT_TO_POINTER (GMENU_TREE_FLAGS_NONE));
4104
4105 tmp = subdirs;
4106 while (tmp != NULL)
4107 {
4108 GMenuTreeDirectory *subdir = tmp->data;
4109
4110 if (GMENU_TREE_ITEM (subdir)->type == GMENU_TREE_ITEM_ALIAS)
4111 {
4112 merge_alias (tree, directory, GMENU_TREE_ALIAS (subdir));
4113 gmenu_tree_item_unref (subdir);
4114 }
4115 else if (!find_name_in_list (subdir->name, except))
4116 {
4117 merge_subdir (tree, directory, subdir);
4118 gmenu_tree_item_unref (subdir);
4119 }
4120 else
4121 {
4122 menu_verbose ("Not merging directory '%s' yet\n", subdir->name);
4123 directory->subdirs = g_slist_append (directory->subdirs, subdir);
4124 }
4125
4126 tmp = tmp->next;
4127 }
4128
4129 g_slist_free (subdirs);
4130 g_slist_free (except);
4131 }
4132
4133 static void
merge_entries(GMenuTree * tree,GMenuTreeDirectory * directory,GSList * except)4134 merge_entries (GMenuTree *tree,
4135 GMenuTreeDirectory *directory,
4136 GSList *except)
4137 {
4138 GSList *entries;
4139 GSList *tmp;
4140
4141 menu_verbose ("Merging entries in directory '%s'\n", directory->name);
4142
4143 entries = directory->entries;
4144 directory->entries = NULL;
4145
4146 entries = g_slist_sort_with_data (entries,
4147 (GCompareDataFunc) gmenu_tree_item_compare,
4148 GINT_TO_POINTER (tree->flags));
4149
4150 tmp = entries;
4151 while (tmp != NULL)
4152 {
4153 GMenuTreeEntry *entry = tmp->data;
4154
4155 if (GMENU_TREE_ITEM (entry)->type == GMENU_TREE_ITEM_ALIAS)
4156 {
4157 merge_alias (tree, directory, GMENU_TREE_ALIAS (entry));
4158 gmenu_tree_item_unref (entry);
4159 }
4160 else if (!find_name_in_list (entry->desktop_file_id, except))
4161 {
4162 merge_entry (tree, directory, entry);
4163 gmenu_tree_item_unref (entry);
4164 }
4165 else
4166 {
4167 menu_verbose ("Not merging entry '%s' yet\n", entry->desktop_file_id);
4168 directory->entries = g_slist_append (directory->entries, entry);
4169 }
4170
4171 tmp = tmp->next;
4172 }
4173
4174 g_slist_free (entries);
4175 g_slist_free (except);
4176 }
4177
4178 static void
merge_subdirs_and_entries(GMenuTree * tree,GMenuTreeDirectory * directory,GSList * except_subdirs,GSList * except_entries)4179 merge_subdirs_and_entries (GMenuTree *tree,
4180 GMenuTreeDirectory *directory,
4181 GSList *except_subdirs,
4182 GSList *except_entries)
4183 {
4184 GSList *items;
4185 GSList *tmp;
4186
4187 menu_verbose ("Merging subdirs and entries together in directory %s\n",
4188 directory->name);
4189
4190 items = g_slist_concat (directory->subdirs, directory->entries);
4191
4192 directory->subdirs = NULL;
4193 directory->entries = NULL;
4194
4195 items = g_slist_sort_with_data (items,
4196 (GCompareDataFunc) gmenu_tree_item_compare,
4197 GINT_TO_POINTER (tree->flags));
4198
4199 tmp = items;
4200 while (tmp != NULL)
4201 {
4202 GMenuTreeItem *item = tmp->data;
4203 GMenuTreeItemType type;
4204
4205 type = item->type;
4206
4207 if (type == GMENU_TREE_ITEM_ALIAS)
4208 {
4209 merge_alias (tree, directory, GMENU_TREE_ALIAS (item));
4210 gmenu_tree_item_unref (item);
4211 }
4212 else if (type == GMENU_TREE_ITEM_DIRECTORY)
4213 {
4214 if (!find_name_in_list (GMENU_TREE_DIRECTORY (item)->name, except_subdirs))
4215 {
4216 merge_subdir (tree,
4217 directory,
4218 GMENU_TREE_DIRECTORY (item));
4219 gmenu_tree_item_unref (item);
4220 }
4221 else
4222 {
4223 menu_verbose ("Not merging directory '%s' yet\n",
4224 GMENU_TREE_DIRECTORY (item)->name);
4225 directory->subdirs = g_slist_append (directory->subdirs, item);
4226 }
4227 }
4228 else if (type == GMENU_TREE_ITEM_ENTRY)
4229 {
4230 if (!find_name_in_list (GMENU_TREE_ENTRY (item)->desktop_file_id, except_entries))
4231 {
4232 merge_entry (tree, directory, GMENU_TREE_ENTRY (item));
4233 gmenu_tree_item_unref (item);
4234 }
4235 else
4236 {
4237 menu_verbose ("Not merging entry '%s' yet\n",
4238 GMENU_TREE_ENTRY (item)->desktop_file_id);
4239 directory->entries = g_slist_append (directory->entries, item);
4240 }
4241 }
4242 else
4243 {
4244 g_assert_not_reached ();
4245 }
4246
4247 tmp = tmp->next;
4248 }
4249
4250 g_slist_free (items);
4251 g_slist_free (except_subdirs);
4252 g_slist_free (except_entries);
4253 }
4254
4255 static GSList *
get_subdirs_from_layout_info(GSList * layout_info)4256 get_subdirs_from_layout_info (GSList *layout_info)
4257 {
4258 GSList *subdirs;
4259 GSList *tmp;
4260
4261 subdirs = NULL;
4262
4263 tmp = layout_info;
4264 while (tmp != NULL)
4265 {
4266 MenuLayoutNode *node = tmp->data;
4267
4268 if (menu_layout_node_get_type (node) == MENU_LAYOUT_NODE_MENUNAME)
4269 {
4270 subdirs = g_slist_append (subdirs,
4271 (char *) menu_layout_node_get_content (node));
4272 }
4273
4274 tmp = tmp->next;
4275 }
4276
4277 return subdirs;
4278 }
4279
4280 static GSList *
get_entries_from_layout_info(GSList * layout_info)4281 get_entries_from_layout_info (GSList *layout_info)
4282 {
4283 GSList *entries;
4284 GSList *tmp;
4285
4286 entries = NULL;
4287
4288 tmp = layout_info;
4289 while (tmp != NULL)
4290 {
4291 MenuLayoutNode *node = tmp->data;
4292
4293 if (menu_layout_node_get_type (node) == MENU_LAYOUT_NODE_FILENAME)
4294 {
4295 entries = g_slist_append (entries,
4296 (char *) menu_layout_node_get_content (node));
4297 }
4298
4299 tmp = tmp->next;
4300 }
4301
4302 return entries;
4303 }
4304
4305 static void
process_layout_info(GMenuTree * tree,GMenuTreeDirectory * directory)4306 process_layout_info (GMenuTree *tree,
4307 GMenuTreeDirectory *directory)
4308 {
4309 GSList *layout_info;
4310
4311 menu_verbose ("Processing menu layout hints for %s\n", directory->name);
4312
4313 g_slist_foreach (directory->contents,
4314 (GFunc) gmenu_tree_item_unref_and_unset_parent,
4315 NULL);
4316 g_slist_free (directory->contents);
4317 directory->contents = NULL;
4318 directory->layout_pending_separator = FALSE;
4319
4320 layout_info = get_layout_info (directory, NULL);
4321
4322 if (layout_info == NULL)
4323 {
4324 merge_subdirs (tree, directory, NULL);
4325 merge_entries (tree, directory, NULL);
4326 }
4327 else
4328 {
4329 GSList *tmp;
4330
4331 tmp = layout_info;
4332 while (tmp != NULL)
4333 {
4334 MenuLayoutNode *node = tmp->data;
4335
4336 switch (menu_layout_node_get_type (node))
4337 {
4338 case MENU_LAYOUT_NODE_MENUNAME:
4339 merge_subdir_by_name (tree,
4340 directory,
4341 menu_layout_node_get_content (node));
4342 break;
4343
4344 case MENU_LAYOUT_NODE_FILENAME:
4345 merge_entry_by_id (tree,
4346 directory,
4347 menu_layout_node_get_content (node));
4348 break;
4349
4350 case MENU_LAYOUT_NODE_SEPARATOR:
4351 /* Unless explicitly told to show all separators, do not show a
4352 * separator at the beginning of a menu. Note that we don't add
4353 * the separators now, and instead make it pending. This way, we
4354 * won't show two consecutive separators nor will we show a
4355 * separator at the end of a menu. */
4356 if (tree->flags & GMENU_TREE_FLAGS_SHOW_ALL_SEPARATORS)
4357 {
4358 directory->layout_pending_separator = TRUE;
4359 check_pending_separator (directory);
4360 }
4361 else if (directory->contents)
4362 {
4363 menu_verbose ("Adding a potential separator in '%s'\n",
4364 directory->name);
4365
4366 directory->layout_pending_separator = TRUE;
4367 }
4368 else
4369 {
4370 menu_verbose ("Skipping separator at the beginning of '%s'\n",
4371 directory->name);
4372 }
4373 break;
4374
4375 case MENU_LAYOUT_NODE_MERGE:
4376 switch (menu_layout_node_merge_get_type (node))
4377 {
4378 case MENU_LAYOUT_MERGE_NONE:
4379 break;
4380
4381 case MENU_LAYOUT_MERGE_MENUS:
4382 merge_subdirs (tree,
4383 directory,
4384 get_subdirs_from_layout_info (tmp->next));
4385 break;
4386
4387 case MENU_LAYOUT_MERGE_FILES:
4388 merge_entries (tree,
4389 directory,
4390 get_entries_from_layout_info (tmp->next));
4391 break;
4392
4393 case MENU_LAYOUT_MERGE_ALL:
4394 merge_subdirs_and_entries (tree,
4395 directory,
4396 get_subdirs_from_layout_info (tmp->next),
4397 get_entries_from_layout_info (tmp->next));
4398 break;
4399
4400 default:
4401 g_assert_not_reached ();
4402 break;
4403 }
4404 break;
4405
4406 default:
4407 g_assert_not_reached ();
4408 break;
4409 }
4410
4411 tmp = tmp->next;
4412 }
4413 }
4414
4415 g_slist_foreach (directory->subdirs,
4416 (GFunc) gmenu_tree_item_unref,
4417 NULL);
4418 g_slist_free (directory->subdirs);
4419 directory->subdirs = NULL;
4420
4421 g_slist_foreach (directory->entries,
4422 (GFunc) gmenu_tree_item_unref,
4423 NULL);
4424 g_slist_free (directory->entries);
4425 directory->entries = NULL;
4426
4427 g_slist_foreach (directory->default_layout_info,
4428 (GFunc) menu_layout_node_unref,
4429 NULL);
4430 g_slist_free (directory->default_layout_info);
4431 directory->default_layout_info = NULL;
4432
4433 g_slist_foreach (directory->layout_info,
4434 (GFunc) menu_layout_node_unref,
4435 NULL);
4436 g_slist_free (directory->layout_info);
4437 directory->layout_info = NULL;
4438 }
4439
4440 static void
handle_entries_changed(MenuLayoutNode * layout,GMenuTree * tree)4441 handle_entries_changed (MenuLayoutNode *layout,
4442 GMenuTree *tree)
4443 {
4444 if (tree->layout == layout)
4445 {
4446 gmenu_tree_force_rebuild (tree);
4447 gmenu_tree_invoke_monitors (tree);
4448 }
4449 }
4450
4451 static void
update_entry_index(GMenuTree * tree,GMenuTreeDirectory * dir)4452 update_entry_index (GMenuTree *tree,
4453 GMenuTreeDirectory *dir)
4454 {
4455 GMenuTreeIter *iter = gmenu_tree_directory_iter (dir);
4456 GMenuTreeItemType next_type;
4457
4458 while ((next_type = gmenu_tree_iter_next (iter)) != GMENU_TREE_ITEM_INVALID)
4459 {
4460 gpointer item = NULL;
4461
4462 switch (next_type)
4463 {
4464 case GMENU_TREE_ITEM_ENTRY:
4465 {
4466 const char *id;
4467
4468 item = gmenu_tree_iter_get_entry (iter);
4469 id = gmenu_tree_entry_get_desktop_file_id (item);
4470 if (id != NULL)
4471 g_hash_table_insert (tree->entries_by_id, (char*)id, item);
4472 }
4473 break;
4474 case GMENU_TREE_ITEM_DIRECTORY:
4475 {
4476 item = gmenu_tree_iter_get_directory (iter);
4477 update_entry_index (tree, (GMenuTreeDirectory*)item);
4478 }
4479 break;
4480 default:
4481 break;
4482 }
4483 if (item != NULL)
4484 gmenu_tree_item_unref (item);
4485 }
4486
4487 gmenu_tree_iter_unref (iter);
4488 }
4489
4490 static gboolean
gmenu_tree_build_from_layout(GMenuTree * tree,GError ** error)4491 gmenu_tree_build_from_layout (GMenuTree *tree,
4492 GError **error)
4493 {
4494 DesktopEntrySet *allocated;
4495
4496 if (tree->root)
4497 return TRUE;
4498
4499 if (!gmenu_tree_load_layout (tree, error))
4500 return FALSE;
4501
4502 menu_verbose ("Building menu tree from layout\n");
4503
4504 allocated = desktop_entry_set_new ();
4505
4506 /* create the menu structure */
4507 tree->root = process_layout (tree,
4508 NULL,
4509 find_menu_child (tree->layout),
4510 allocated);
4511 if (tree->root)
4512 {
4513 DesktopEntrySet *unallocated_used;
4514
4515 unallocated_used = desktop_entry_set_new ();
4516
4517 process_only_unallocated (tree, tree->root, allocated, unallocated_used);
4518 if (tree->flags & GMENU_TREE_FLAGS_INCLUDE_UNALLOCATED)
4519 {
4520 DesktopEntrySet *entry_pool;
4521 DesktopEntrySet *still_unallocated;
4522 GetStillUnallocatedForeachData data;
4523
4524 entry_pool = _entry_directory_list_get_all_desktops (menu_layout_node_menu_get_app_dirs (find_menu_child (tree->layout)));
4525 still_unallocated = desktop_entry_set_new ();
4526
4527 data.tree = tree;
4528 data.allocated = allocated;
4529 data.unallocated_used = unallocated_used;
4530 data.still_unallocated = still_unallocated;
4531
4532 desktop_entry_set_foreach (entry_pool,
4533 (DesktopEntrySetForeachFunc) get_still_unallocated_foreach,
4534 &data);
4535
4536 desktop_entry_set_unref (entry_pool);
4537
4538 desktop_entry_set_foreach (still_unallocated,
4539 (DesktopEntrySetForeachFunc) unallocated_entries_listify_foreach,
4540 tree->root);
4541
4542 desktop_entry_set_unref (still_unallocated);
4543 }
4544
4545 desktop_entry_set_unref (unallocated_used);
4546
4547 /* process the layout info part that can move/remove items:
4548 * inline, show_empty, etc. */
4549 preprocess_layout_info (tree, tree->root);
4550 /* populate the menu structure that we got with the items, and order it
4551 * according to the layout info */
4552 process_layout_info (tree, tree->root);
4553
4554 update_entry_index (tree, tree->root);
4555
4556 menu_layout_node_root_add_entries_monitor (tree->layout,
4557 (MenuLayoutNodeEntriesChangedFunc) handle_entries_changed,
4558 tree);
4559 }
4560
4561 desktop_entry_set_unref (allocated);
4562
4563 return TRUE;
4564 }
4565
4566 static void
gmenu_tree_force_rebuild(GMenuTree * tree)4567 gmenu_tree_force_rebuild (GMenuTree *tree)
4568 {
4569 if (tree->root)
4570 {
4571 g_hash_table_remove_all (tree->entries_by_id);
4572 gmenu_tree_item_unref (tree->root);
4573 tree->root = NULL;
4574 tree->loaded = FALSE;
4575
4576 g_assert (tree->layout != NULL);
4577
4578 menu_layout_node_root_remove_entries_monitor (tree->layout,
4579 (MenuLayoutNodeEntriesChangedFunc) handle_entries_changed,
4580 tree);
4581 }
4582 }
4583
4584 GType
gmenu_tree_iter_get_type(void)4585 gmenu_tree_iter_get_type (void)
4586 {
4587 static GType gtype = G_TYPE_INVALID;
4588 if (gtype == G_TYPE_INVALID)
4589 {
4590 gtype = g_boxed_type_register_static ("GMenuTreeIter",
4591 (GBoxedCopyFunc)gmenu_tree_iter_ref,
4592 (GBoxedFreeFunc)gmenu_tree_iter_unref);
4593 }
4594 return gtype;
4595 }
4596
4597 GType
gmenu_tree_directory_get_type(void)4598 gmenu_tree_directory_get_type (void)
4599 {
4600 static GType gtype = G_TYPE_INVALID;
4601 if (gtype == G_TYPE_INVALID)
4602 {
4603 gtype = g_boxed_type_register_static ("GMenuTreeDirectory",
4604 (GBoxedCopyFunc)gmenu_tree_item_ref,
4605 (GBoxedFreeFunc)gmenu_tree_item_unref);
4606 }
4607 return gtype;
4608 }
4609
4610 GType
gmenu_tree_entry_get_type(void)4611 gmenu_tree_entry_get_type (void)
4612 {
4613 static GType gtype = G_TYPE_INVALID;
4614 if (gtype == G_TYPE_INVALID)
4615 {
4616 gtype = g_boxed_type_register_static ("GMenuTreeEntry",
4617 (GBoxedCopyFunc)gmenu_tree_item_ref,
4618 (GBoxedFreeFunc)gmenu_tree_item_unref);
4619 }
4620 return gtype;
4621 }
4622
4623 GType
gmenu_tree_separator_get_type(void)4624 gmenu_tree_separator_get_type (void)
4625 {
4626 static GType gtype = G_TYPE_INVALID;
4627 if (gtype == G_TYPE_INVALID)
4628 {
4629 gtype = g_boxed_type_register_static ("GMenuTreeSeparator",
4630 (GBoxedCopyFunc)gmenu_tree_item_ref,
4631 (GBoxedFreeFunc)gmenu_tree_item_unref);
4632 }
4633 return gtype;
4634 }
4635
4636 GType
gmenu_tree_header_get_type(void)4637 gmenu_tree_header_get_type (void)
4638 {
4639 static GType gtype = G_TYPE_INVALID;
4640 if (gtype == G_TYPE_INVALID)
4641 {
4642 gtype = g_boxed_type_register_static ("GMenuTreeHeader",
4643 (GBoxedCopyFunc)gmenu_tree_item_ref,
4644 (GBoxedFreeFunc)gmenu_tree_item_unref);
4645 }
4646 return gtype;
4647 }
4648
4649 GType
gmenu_tree_alias_get_type(void)4650 gmenu_tree_alias_get_type (void)
4651 {
4652 static GType gtype = G_TYPE_INVALID;
4653 if (gtype == G_TYPE_INVALID)
4654 {
4655 gtype = g_boxed_type_register_static ("GMenuTreeAlias",
4656 (GBoxedCopyFunc)gmenu_tree_item_ref,
4657 (GBoxedFreeFunc)gmenu_tree_item_unref);
4658 }
4659 return gtype;
4660 }
4661
4662 GType
gmenu_tree_flags_get_type(void)4663 gmenu_tree_flags_get_type (void)
4664 {
4665 static GType enum_type_id = 0;
4666 if (G_UNLIKELY (!enum_type_id))
4667 {
4668 static const GFlagsValue values[] = {
4669 { GMENU_TREE_FLAGS_NONE, "GMENU_TREE_FLAGS_NONE", "none" },
4670 { GMENU_TREE_FLAGS_INCLUDE_EXCLUDED, "GMENU_TREE_FLAGS_INCLUDE_EXCLUDED", "include-excluded" },
4671 { GMENU_TREE_FLAGS_SHOW_EMPTY, "GMENU_TREE_FLAGS_SHOW_EMPTY", "show-empty" },
4672 { GMENU_TREE_FLAGS_INCLUDE_NODISPLAY, "GMENU_TREE_FLAGS_INCLUDE_NODISPLAY", "include-nodisplay" },
4673 { GMENU_TREE_FLAGS_SHOW_ALL_SEPARATORS, "GMENU_TREE_FLAGS_SHOW_ALL_SEPARATORS", "show-all-separators" },
4674 { GMENU_TREE_FLAGS_SORT_DISPLAY_NAME, "GMENU_TREE_FLAGS_SORT_DISPLAY_NAME", "sort-display-name" },
4675 { GMENU_TREE_FLAGS_INCLUDE_UNALLOCATED, "GMENU_TREE_FLAGS_INCLUDE_UNALLOCATED,", "include-unallocated" },
4676 { 0, NULL, NULL }
4677 };
4678 enum_type_id = g_flags_register_static ("GMenuTreeFlags", values);
4679 }
4680 return enum_type_id;
4681 }
4682