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