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