summaryrefslogtreecommitdiff
path: root/libmenu
diff options
context:
space:
mode:
Diffstat (limited to 'libmenu')
-rw-r--r--libmenu/Makefile.am78
-rw-r--r--libmenu/canonicalize.c326
-rw-r--r--libmenu/canonicalize.h38
-rw-r--r--libmenu/desktop-entries.c816
-rw-r--r--libmenu/desktop-entries.h90
-rw-r--r--libmenu/entry-directories.c1105
-rw-r--r--libmenu/entry-directories.h67
-rw-r--r--libmenu/libmate-menu-uninstalled.pc.in11
-rw-r--r--libmenu/libmate-menu.pc.in11
-rw-r--r--libmenu/matemenu-tree.c4520
-rw-r--r--libmenu/matemenu-tree.h135
-rw-r--r--libmenu/menu-layout.c2359
-rw-r--r--libmenu/menu-layout.h161
-rw-r--r--libmenu/menu-monitor.c440
-rw-r--r--libmenu/menu-monitor.h70
-rw-r--r--libmenu/menu-util.c436
-rw-r--r--libmenu/menu-util.h59
17 files changed, 10722 insertions, 0 deletions
diff --git a/libmenu/Makefile.am b/libmenu/Makefile.am
new file mode 100644
index 0000000..3d06096
--- /dev/null
+++ b/libmenu/Makefile.am
@@ -0,0 +1,78 @@
+lib_LTLIBRARIES = libmate-menu.la
+
+AM_CPPFLAGS = \
+ $(GLIB_CFLAGS) \
+ -DMATEMENU_I_KNOW_THIS_IS_UNSTABLE \
+ $(DISABLE_DEPRECATED_CFLAGS) \
+ $(DEBUG_CFLAGS)
+
+AM_CFLAGS = $(WARN_CFLAGS)
+
+libmate_menu_includedir = $(includedir)/mate-menus
+libmate_menu_include_HEADERS = \
+ matemenu-tree.h
+
+libmate_menu_sources = \
+ canonicalize.c \
+ desktop-entries.c \
+ entry-directories.c \
+ matemenu-tree.c \
+ menu-layout.c \
+ menu-monitor.c \
+ menu-util.c
+
+libmate_menu_la_SOURCES = \
+ $(libmate_menu_sources) \
+ canonicalize.h \
+ desktop-entries.h \
+ entry-directories.h \
+ matemenu-tree.h \
+ menu-layout.h \
+ menu-monitor.h \
+ menu-util.h
+
+libmate_menu_la_LIBADD = \
+ $(GLIB_LIBS)
+
+libmate_menu_la_LDFLAGS = \
+ -version-info $(LIB_MENU_LT_VERSION) \
+ -no-undefined \
+ -export-symbols-regex matemenu_tree
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libmate-menu.pc
+
+EXTRA_DIST = \
+ libmate-menu.pc.in \
+ libmate-menu-uninstalled.pc.in
+
+CLEANFILES =
+
+# Introspection
+-include $(INTROSPECTION_MAKEFILE)
+INTROSPECTION_GIRS =
+INTROSPECTION_SCANNER_ARGS = --warn-all --add-include-path=$(srcdir)
+INTROSPECTION_COMPILER_ARGS = --includedir=$(srcdir)
+
+if HAVE_INTROSPECTION
+# Note: we only include the headers here so far because there's no gtk-doc at all anyway
+introspection_sources = $(libmate_menu_include_HEADERS)
+
+MateMenu-2.0.gir: libmate-menu.la
+MateMenu_2_0_gir_INCLUDES = GObject-2.0
+MateMenu_2_0_gir_CFLAGS = $(AM_CPPFLAGS)
+MateMenu_2_0_gir_LIBS = libmate-menu.la
+MateMenu_2_0_gir_SCANNERFLAGS = --pkg-export=libmate-menu
+MateMenu_2_0_gir_FILES = $(addprefix $(srcdir)/,$(introspection_sources))
+INTROSPECTION_GIRS += MateMenu-2.0.gir
+
+girdir = $(INTROSPECTION_GIRDIR)
+gir_DATA = $(INTROSPECTION_GIRS)
+
+typelibdir = $(INTROSPECTION_TYPELIBDIR)
+typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)
+
+CLEANFILES += $(gir_DATA) $(typelib_DATA)
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/libmenu/canonicalize.c b/libmenu/canonicalize.c
new file mode 100644
index 0000000..82082e9
--- /dev/null
+++ b/libmenu/canonicalize.c
@@ -0,0 +1,326 @@
+/* Return the canonical absolute name of a given file.
+ * Copyright (C) 1996-2001, 2002 Free Software Foundation, Inc.
+ * This file is part of the GNU C Library.
+ *
+ * Copyright (C) 2002 Red Hat, Inc. (trivial port to GLib)
+ *
+ * The GNU C Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * The GNU C Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the GNU C Library; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ */
+
+#include <config.h>
+
+#include "canonicalize.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stddef.h>
+
+/* Return the canonical absolute name of file NAME. A canonical name
+ does not contain any `.', `..' components nor any repeated path
+ separators ('/') or symlinks. All path components must exist. If
+ RESOLVED is null, the result is malloc'd; otherwise, if the
+ canonical name is PATH_MAX chars or more, returns null with `errno'
+ set to ENAMETOOLONG; if the name fits in fewer than PATH_MAX chars,
+ returns the name in RESOLVED. If the name cannot be resolved and
+ RESOLVED is non-NULL, it contains the path of the first component
+ that cannot be resolved. If the path can be resolved, RESOLVED
+ holds the same value as the value returned. */
+
+static char* menu_realpath(const char* name, char* resolved)
+{
+ char* rpath = NULL;
+ char* dest = NULL;
+ char* extra_buf = NULL;
+ const char* start;
+ const char* end;
+ const char* rpath_limit;
+ long int path_max;
+ int num_links = 0;
+
+ if (name == NULL)
+ {
+ /* As per Single Unix Specification V2 we must return an error if
+ * either parameter is a null pointer. We extend this to allow
+ * the RESOLVED parameter to be NULL in case the we are expected to
+ * allocate the room for the return value. */
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (name[0] == '\0')
+ {
+ /* As per Single Unix Specification V2 we must return an error if
+ * the name argument points to an empty string. */
+ errno = ENOENT;
+ return NULL;
+ }
+
+ #ifdef PATH_MAX
+ path_max = PATH_MAX;
+ #else
+ path_max = pathconf(name, _PC_PATH_MAX);
+
+ if (path_max <= 0)
+ {
+ path_max = 1024;
+ }
+ #endif
+
+ rpath = resolved ? g_alloca(path_max) : g_malloc(path_max);
+ rpath_limit = rpath + path_max;
+
+ if (name[0] != G_DIR_SEPARATOR)
+ {
+ if (!getcwd(rpath, path_max))
+ {
+ rpath[0] = '\0';
+ goto error;
+ }
+
+ dest = strchr(rpath, '\0');
+ }
+ else
+ {
+ rpath[0] = G_DIR_SEPARATOR;
+ dest = rpath + 1;
+ }
+
+ for (start = end = name; *start; start = end)
+ {
+ struct stat st;
+ int n;
+
+ /* Skip sequence of multiple path-separators. */
+ while (*start == G_DIR_SEPARATOR)
+ {
+ ++start;
+ }
+
+ /* Find end of path component. */
+ for (end = start; *end && *end != G_DIR_SEPARATOR; ++end)
+ {
+ /* Nothing. */;
+ }
+
+ if (end - start == 0)
+ {
+ break;
+ }
+ else if (end - start == 1 && start[0] == '.')
+ {
+ /* nothing */;
+ }
+ else if (end - start == 2 && start[0] == '.' && start[1] == '.')
+ {
+ /* Back up to previous component, ignore if at root already. */
+ if (dest > rpath + 1)
+ {
+ while ((--dest)[-1] != G_DIR_SEPARATOR)
+ {
+ /* Nothing. */;
+ }
+ }
+ }
+ else
+ {
+ size_t new_size;
+
+ if (dest[-1] != G_DIR_SEPARATOR)
+ {
+ *dest++ = G_DIR_SEPARATOR;
+ }
+
+ if (dest + (end - start) >= rpath_limit)
+ {
+ char* new_rpath;
+ ptrdiff_t dest_offset = dest - rpath;
+
+ if (resolved)
+ {
+ #ifdef ENAMETOOLONG
+ errno = ENAMETOOLONG;
+ #else
+ /* Uh... just pick something */
+ errno = EINVAL;
+ #endif
+
+ if (dest > rpath + 1)
+ {
+ dest--;
+ }
+
+ *dest = '\0';
+ goto error;
+ }
+
+ new_size = rpath_limit - rpath;
+
+ if (end - start + 1 > path_max)
+ {
+ new_size += end - start + 1;
+ }
+ else
+ {
+ new_size += path_max;
+ }
+
+ new_rpath = (char*) realloc(rpath, new_size);
+
+ if (new_rpath == NULL)
+ {
+ goto error;
+ }
+
+ rpath = new_rpath;
+ rpath_limit = rpath + new_size;
+
+ dest = rpath + dest_offset;
+ }
+
+ memcpy(dest, start, end - start);
+ dest = dest + (end - start);
+ *dest = '\0';
+
+ if (stat(rpath, &st) < 0)
+ {
+ goto error;
+ }
+
+ if (S_ISLNK(st.st_mode))
+ {
+ char* buf = alloca(path_max);
+ size_t len;
+
+ if (++num_links > MAXSYMLINKS)
+ {
+ errno = ELOOP;
+ goto error;
+ }
+
+ n = readlink(rpath, buf, path_max);
+
+ if (n < 0)
+ {
+ goto error;
+ }
+
+ buf[n] = '\0';
+
+
+
+ if (!extra_buf)
+ {
+ extra_buf = g_alloca(path_max);
+ }
+
+ len = strlen(end);
+
+ if ((long int) (n + len) >= path_max)
+ {
+ #ifdef ENAMETOOLONG
+ errno = ENAMETOOLONG;
+ #else
+ /* Uh... just pick something */
+ errno = EINVAL;
+ #endif
+
+ goto error;
+ }
+
+ /* Careful here, end may be a pointer into extra_buf... */
+ g_memmove(&extra_buf[n], end, len + 1);
+ name = end = memcpy(extra_buf, buf, n);
+
+ if (buf[0] == G_DIR_SEPARATOR)
+ {
+ dest = rpath + 1; /* It's an absolute symlink */
+ }
+ else
+ {
+ /* Back up to previous component, ignore if at root already: */
+ if (dest > rpath + 1)
+ {
+ while ((--dest)[-1] != G_DIR_SEPARATOR)
+ {
+ /* Nothing. */;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (dest > rpath + 1 && dest[-1] == G_DIR_SEPARATOR)
+ {
+ --dest;
+ }
+
+ *dest = '\0';
+
+ return resolved ? memcpy(resolved, rpath, dest - rpath + 1) : rpath;
+
+ error:
+
+ if (resolved)
+ {
+ strcpy(resolved, rpath);
+ }
+ else
+ {
+ g_free(rpath);
+ }
+
+ return NULL;
+}
+
+char* menu_canonicalize_file_name(const char* name, gboolean allow_missing_basename)
+{
+ char* retval;
+
+ retval = menu_realpath(name, NULL);
+
+ /* We could avoid some system calls by using the second
+ * argument to realpath() instead of doing realpath
+ * all over again, but who cares really. we'll see if
+ * it's ever in a profile.
+ */
+ if (allow_missing_basename && retval == NULL)
+ {
+ char* dirname;
+ char* canonical_dirname;
+
+ dirname = g_path_get_dirname(name);
+ canonical_dirname = menu_realpath(dirname, NULL);
+ g_free(dirname);
+
+ if (canonical_dirname)
+ {
+ char* basename;
+
+ basename = g_path_get_basename(name);
+ retval = g_build_filename(canonical_dirname, basename, NULL);
+ g_free(basename);
+ g_free(canonical_dirname);
+ }
+ }
+
+ return retval;
+}
diff --git a/libmenu/canonicalize.h b/libmenu/canonicalize.h
new file mode 100644
index 0000000..b5deddc
--- /dev/null
+++ b/libmenu/canonicalize.h
@@ -0,0 +1,38 @@
+/* Return the canonical absolute name of a given file.
+ * Copyright (C) 1996-2001, 2002 Free Software Foundation, Inc.
+ * This file is part of the GNU C Library.
+ *
+ * Copyright (C) 2002 Red Hat, Inc. (trivial port to GLib)
+ *
+ * The GNU C Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * The GNU C Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the GNU C Library; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ */
+
+#ifndef G_CANONICALIZE_H
+#define G_CANONICALIZE_H
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+char* menu_canonicalize_file_name(const char* name, gboolean allow_missing_basename);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* G_CANONICALIZE_H */
diff --git a/libmenu/desktop-entries.c b/libmenu/desktop-entries.c
new file mode 100644
index 0000000..9488bde
--- /dev/null
+++ b/libmenu/desktop-entries.c
@@ -0,0 +1,816 @@
+/*
+ * Copyright (C) 2002 - 2004 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include "desktop-entries.h"
+
+#include <string.h>
+
+#include "menu-util.h"
+
+#define DESKTOP_ENTRY_GROUP "Desktop Entry"
+#define KDE_DESKTOP_ENTRY_GROUP "KDE Desktop Entry"
+
+enum {
+ DESKTOP_ENTRY_NO_DISPLAY = 1 << 0,
+ DESKTOP_ENTRY_HIDDEN = 1 << 1,
+ DESKTOP_ENTRY_SHOW_IN_MATE = 1 << 2,
+ DESKTOP_ENTRY_TRYEXEC_FAILED = 1 << 3
+};
+
+struct DesktopEntry {
+ char* path;
+ char* basename;
+
+ GQuark* categories;
+
+ char* name;
+ char* generic_name;
+ char* full_name;
+ char* comment;
+ char* icon;
+ char* exec;
+ gboolean terminal;
+
+ guint type: 2;
+ guint flags: 4;
+ guint refcount: 24;
+};
+
+struct DesktopEntrySet {
+ int refcount;
+ GHashTable* hash;
+};
+
+/*
+ * Desktop entries
+ */
+
+static guint get_flags_from_key_file(DesktopEntry* entry, GKeyFile* key_file, const char* desktop_entry_group)
+{
+ GError *error;
+ char **strv;
+ gboolean no_display;
+ gboolean hidden;
+ gboolean show_in_mate;
+ gboolean tryexec_failed;
+ char *tryexec;
+ guint flags;
+ int i;
+
+ error = NULL;
+ no_display = g_key_file_get_boolean (key_file,
+ desktop_entry_group,
+ "NoDisplay",
+ &error);
+ if (error)
+ {
+ no_display = FALSE;
+ g_error_free (error);
+ }
+
+ error = NULL;
+ hidden = g_key_file_get_boolean (key_file,
+ desktop_entry_group,
+ "Hidden",
+ &error);
+ if (error)
+ {
+ hidden = FALSE;
+ g_error_free (error);
+ }
+
+ show_in_mate = TRUE;
+ strv = g_key_file_get_string_list (key_file,
+ desktop_entry_group,
+ "OnlyShowIn",
+ NULL,
+ NULL);
+ if (strv)
+ {
+ show_in_mate = FALSE;
+ for (i = 0; strv[i]; i++)
+ {
+ if (!strcmp (strv[i], "MATE"))
+ {
+ show_in_mate = TRUE;
+ break;
+ }
+ }
+ }
+ else
+ {
+ strv = g_key_file_get_string_list (key_file,
+ desktop_entry_group,
+ "NotShowIn",
+ NULL,
+ NULL);
+ if (strv)
+ {
+ show_in_mate = TRUE;
+ for (i = 0; strv[i]; i++)
+ {
+ if (!strcmp (strv[i], "MATE"))
+ {
+ show_in_mate = FALSE;
+ }
+ }
+ }
+ }
+ g_strfreev (strv);
+
+ tryexec_failed = FALSE;
+ tryexec = g_key_file_get_string (key_file,
+ desktop_entry_group,
+ "TryExec",
+ NULL);
+ if (tryexec)
+ {
+ char *path;
+
+ path = g_find_program_in_path (g_strstrip (tryexec));
+
+ tryexec_failed = (path == NULL);
+
+ g_free (path);
+ g_free (tryexec);
+ }
+
+ flags = 0;
+ if (no_display)
+ flags |= DESKTOP_ENTRY_NO_DISPLAY;
+ if (hidden)
+ flags |= DESKTOP_ENTRY_HIDDEN;
+ if (show_in_mate)
+ flags |= DESKTOP_ENTRY_SHOW_IN_MATE;
+ if (tryexec_failed)
+ flags |= DESKTOP_ENTRY_TRYEXEC_FAILED;
+
+ return flags;
+}
+
+static GQuark* get_categories_from_key_file (DesktopEntry* entry, GKeyFile* key_file, const char* desktop_entry_group)
+{
+ GQuark *retval;
+ char **strv;
+ gsize len;
+ int i;
+
+ strv = g_key_file_get_string_list (key_file,
+ desktop_entry_group,
+ "Categories",
+ &len,
+ NULL);
+ if (!strv)
+ return NULL;
+
+ retval = g_new0 (GQuark, len + 1);
+
+ for (i = 0; strv[i]; i++)
+ retval[i] = g_quark_from_string (strv[i]);
+
+ g_strfreev (strv);
+
+ return retval;
+}
+
+static DesktopEntry* desktop_entry_load(DesktopEntry* entry)
+{
+ DesktopEntry *retval = NULL;
+ GKeyFile *key_file;
+ GError *error;
+ const char *desktop_entry_group;
+ char *name_str;
+ char *type_str;
+
+ key_file = g_key_file_new ();
+
+ error = NULL;
+ if (!g_key_file_load_from_file (key_file, entry->path, 0, &error))
+ {
+ menu_verbose ("Failed to load \"%s\": %s\n",
+ entry->path, error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ if (g_key_file_has_group (key_file, DESKTOP_ENTRY_GROUP))
+ {
+ desktop_entry_group = DESKTOP_ENTRY_GROUP;
+ }
+ else
+ {
+ menu_verbose ("\"%s\" contains no \"" DESKTOP_ENTRY_GROUP "\" group\n",
+ entry->path);
+
+ if (g_key_file_has_group (key_file, KDE_DESKTOP_ENTRY_GROUP))
+ {
+ desktop_entry_group = KDE_DESKTOP_ENTRY_GROUP;
+ menu_verbose ("\"%s\" contains deprecated \"" KDE_DESKTOP_ENTRY_GROUP "\" group\n",
+ entry->path);
+ }
+ else
+ {
+ goto out;
+ }
+ }
+
+ if (!g_key_file_has_key (key_file, desktop_entry_group, "Name", NULL))
+ {
+ menu_verbose ("\"%s\" contains no \"Name\" key\n", entry->path);
+ goto out;
+ }
+
+ name_str = g_key_file_get_locale_string (key_file, desktop_entry_group, "Name", NULL, NULL);
+ if (!name_str)
+ {
+ menu_verbose ("\"%s\" contains an invalid \"Name\" key\n", entry->path);
+ goto out;
+ }
+
+ g_free (name_str);
+
+ type_str = g_key_file_get_string (key_file, desktop_entry_group, "Type", NULL);
+ if (!type_str)
+ {
+ menu_verbose ("\"%s\" contains no \"Type\" key\n", entry->path);
+ goto out;
+ }
+
+ if ((entry->type == DESKTOP_ENTRY_DESKTOP && strcmp (type_str, "Application") != 0) ||
+ (entry->type == DESKTOP_ENTRY_DIRECTORY && strcmp (type_str, "Directory") != 0))
+ {
+ menu_verbose ("\"%s\" does not contain the correct \"Type\" value\n", entry->path);
+ g_free (type_str);
+ goto out;
+ }
+
+ g_free (type_str);
+
+ if (entry->type == DESKTOP_ENTRY_DESKTOP &&
+ !g_key_file_has_key (key_file, desktop_entry_group, "Exec", NULL))
+ {
+ menu_verbose ("\"%s\" does not contain an \"Exec\" key\n", entry->path);
+ goto out;
+ }
+
+ retval = entry;
+
+#define GET_LOCALE_STRING(n) g_key_file_get_locale_string (key_file, desktop_entry_group, (n), NULL, NULL)
+
+ retval->name = GET_LOCALE_STRING ("Name");
+ retval->generic_name = GET_LOCALE_STRING ("GenericName");
+ retval->full_name = GET_LOCALE_STRING ("X-MATE-FullName");
+ retval->comment = GET_LOCALE_STRING ("Comment");
+ retval->icon = GET_LOCALE_STRING ("Icon");
+ retval->flags = get_flags_from_key_file (retval, key_file, desktop_entry_group);
+ retval->categories = get_categories_from_key_file (retval, key_file, desktop_entry_group);
+
+ if (entry->type == DESKTOP_ENTRY_DESKTOP)
+ {
+ retval->exec = g_key_file_get_string (key_file, desktop_entry_group, "Exec", NULL);
+ retval->terminal = g_key_file_get_boolean (key_file, desktop_entry_group, "Terminal", NULL);
+ }
+
+#undef GET_LOCALE_STRING
+
+ menu_verbose ("Desktop entry \"%s\" (%s, %s, %s, %s, %s) flags: NoDisplay=%s, Hidden=%s, ShowInMATE=%s, TryExecFailed=%s\n",
+ retval->basename,
+ retval->name,
+ retval->generic_name ? retval->generic_name : "(null)",
+ retval->full_name ? retval->full_name : "(null)",
+ retval->comment ? retval->comment : "(null)",
+ retval->icon ? retval->icon : "(null)",
+ retval->flags & DESKTOP_ENTRY_NO_DISPLAY ? "(true)" : "(false)",
+ retval->flags & DESKTOP_ENTRY_HIDDEN ? "(true)" : "(false)",
+ retval->flags & DESKTOP_ENTRY_SHOW_IN_MATE ? "(true)" : "(false)",
+ retval->flags & DESKTOP_ENTRY_TRYEXEC_FAILED ? "(true)" : "(false)");
+
+ out:
+ g_key_file_free (key_file);
+
+ if (!retval)
+ desktop_entry_unref (entry);
+
+ return retval;
+}
+
+DesktopEntry* desktop_entry_new(const char* path)
+{
+ DesktopEntryType type;
+ DesktopEntry *retval;
+
+ menu_verbose ("Loading desktop entry \"%s\"\n", path);
+
+ if (g_str_has_suffix (path, ".desktop"))
+ {
+ type = DESKTOP_ENTRY_DESKTOP;
+ }
+ else if (g_str_has_suffix (path, ".directory"))
+ {
+ type = DESKTOP_ENTRY_DIRECTORY;
+ }
+ else
+ {
+ menu_verbose ("Unknown desktop entry suffix in \"%s\"\n",
+ path);
+ return NULL;
+ }
+
+ retval = g_new0 (DesktopEntry, 1);
+
+ retval->refcount = 1;
+ retval->type = type;
+ retval->basename = g_path_get_basename (path);
+ retval->path = g_strdup (path);
+
+ return desktop_entry_load (retval);
+}
+
+DesktopEntry* desktop_entry_reload(DesktopEntry* entry)
+{
+ g_return_val_if_fail (entry != NULL, NULL);
+
+ menu_verbose ("Re-loading desktop entry \"%s\"\n", entry->path);
+
+ g_free (entry->categories);
+ entry->categories = NULL;
+
+ g_free (entry->name);
+ entry->name = NULL;
+
+ g_free (entry->generic_name);
+ entry->generic_name = NULL;
+
+ g_free (entry->full_name);
+ entry->full_name = NULL;
+
+ g_free (entry->comment);
+ entry->comment = NULL;
+
+ g_free (entry->icon);
+ entry->icon = NULL;
+
+ g_free (entry->exec);
+ entry->exec = NULL;
+
+ entry->terminal = 0;
+ entry->flags = 0;
+
+ return desktop_entry_load (entry);
+}
+
+DesktopEntry* desktop_entry_ref(DesktopEntry* entry)
+{
+ g_return_val_if_fail (entry != NULL, NULL);
+ g_return_val_if_fail (entry->refcount > 0, NULL);
+
+ entry->refcount += 1;
+
+ return entry;
+}
+
+DesktopEntry* desktop_entry_copy(DesktopEntry* entry)
+{
+ DesktopEntry *retval;
+ int i;
+
+ menu_verbose ("Copying desktop entry \"%s\"\n",
+ entry->basename);
+
+ retval = g_new0 (DesktopEntry, 1);
+
+ retval->refcount = 1;
+ retval->type = entry->type;
+ retval->basename = g_strdup (entry->basename);
+ retval->path = g_strdup (entry->path);
+ retval->name = g_strdup (entry->name);
+ retval->generic_name = g_strdup (entry->generic_name);
+ retval->full_name = g_strdup (entry->full_name);
+ retval->comment = g_strdup (entry->comment);
+ retval->icon = g_strdup (entry->icon);
+ retval->exec = g_strdup (entry->exec);
+ retval->terminal = entry->terminal;
+ retval->flags = entry->flags;
+
+ i = 0;
+ if (entry->categories != NULL)
+ {
+ for (; entry->categories[i]; i++);
+ }
+
+ retval->categories = g_new0 (GQuark, i + 1);
+
+ i = 0;
+ if (entry->categories != NULL)
+ {
+ for (; entry->categories[i]; i++)
+ retval->categories[i] = entry->categories[i];
+ }
+
+ return retval;
+}
+
+void desktop_entry_unref(DesktopEntry* entry)
+{
+ g_return_if_fail (entry != NULL);
+ g_return_if_fail (entry->refcount > 0);
+
+ entry->refcount -= 1;
+ if (entry->refcount == 0)
+ {
+ g_free (entry->categories);
+ entry->categories = NULL;
+
+ g_free (entry->name);
+ entry->name = NULL;
+
+ g_free (entry->generic_name);
+ entry->generic_name = NULL;
+
+ g_free (entry->full_name);
+ entry->full_name = NULL;
+
+ g_free (entry->comment);
+ entry->comment = NULL;
+
+ g_free (entry->icon);
+ entry->icon = NULL;
+
+ g_free (entry->exec);
+ entry->exec = NULL;
+
+ g_free (entry->basename);
+ entry->basename = NULL;
+
+ g_free (entry->path);
+ entry->path = NULL;
+
+ g_free (entry);
+ }
+}
+
+DesktopEntryType desktop_entry_get_type(DesktopEntry* entry)
+{
+ return entry->type;
+}
+
+const char* desktop_entry_get_path(DesktopEntry* entry)
+{
+ return entry->path;
+}
+
+const char *
+desktop_entry_get_basename (DesktopEntry *entry)
+{
+ return entry->basename;
+}
+
+const char* desktop_entry_get_name(DesktopEntry* entry)
+{
+ return entry->name;
+}
+
+const char* desktop_entry_get_generic_name(DesktopEntry* entry)
+{
+ return entry->generic_name;
+}
+
+const char* desktop_entry_get_full_name(DesktopEntry* entry)
+{
+ return entry->full_name;
+}
+
+const char* desktop_entry_get_comment(DesktopEntry* entry)
+{
+ return entry->comment;
+}
+
+const char* desktop_entry_get_icon(DesktopEntry* entry)
+{
+ return entry->icon;
+}
+
+const char* desktop_entry_get_exec(DesktopEntry* entry)
+{
+ return entry->exec;
+}
+
+gboolean desktop_entry_get_launch_in_terminal(DesktopEntry* entry)
+{
+ return entry->terminal;
+}
+
+gboolean desktop_entry_get_hidden(DesktopEntry* entry)
+{
+ return (entry->flags & DESKTOP_ENTRY_HIDDEN) != 0;
+}
+
+gboolean desktop_entry_get_no_display(DesktopEntry* entry)
+{
+ return (entry->flags & DESKTOP_ENTRY_NO_DISPLAY) != 0;
+}
+
+gboolean desktop_entry_get_show_in_mate(DesktopEntry* entry)
+{
+ return (entry->flags & DESKTOP_ENTRY_SHOW_IN_MATE) != 0;
+}
+
+gboolean desktop_entry_get_tryexec_failed(DesktopEntry* entry)
+{
+ return (entry->flags & DESKTOP_ENTRY_TRYEXEC_FAILED) != 0;
+}
+
+gboolean desktop_entry_has_categories(DesktopEntry* entry)
+{
+ return (entry->categories != NULL && entry->categories[0] != 0);
+}
+
+gboolean desktop_entry_has_category(DesktopEntry* entry, const char* category)
+{
+ GQuark quark;
+ int i;
+
+ if (entry->categories == NULL)
+ return FALSE;
+
+ if (!(quark = g_quark_try_string (category)))
+ return FALSE;
+
+ for (i = 0; entry->categories[i]; i++)
+ {
+ if (quark == entry->categories[i])
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void desktop_entry_add_legacy_category(DesktopEntry* entry)
+{
+ GQuark *categories;
+ int i;
+
+ menu_verbose ("Adding Legacy category to \"%s\"\n",
+ entry->basename);
+
+ i = 0;
+ if (entry->categories != NULL)
+ {
+ for (; entry->categories[i]; i++);
+ }
+
+ categories = g_new0 (GQuark, i + 2);
+
+ i = 0;
+ if (entry->categories != NULL)
+ {
+ for (; entry->categories[i]; i++)
+ categories[i] = entry->categories[i];
+ }
+
+ categories[i] = g_quark_from_string ("Legacy");
+
+ g_free (entry->categories);
+ entry->categories = categories;
+}
+
+/*
+ * Entry sets
+ */
+
+DesktopEntrySet* desktop_entry_set_new(void)
+{
+ DesktopEntrySet *set;
+
+ set = g_new0 (DesktopEntrySet, 1);
+ set->refcount = 1;
+
+ menu_verbose (" New entry set %p\n", set);
+
+ return set;
+}
+
+DesktopEntrySet* desktop_entry_set_ref(DesktopEntrySet* set)
+{
+ g_return_val_if_fail (set != NULL, NULL);
+ g_return_val_if_fail (set->refcount > 0, NULL);
+
+ set->refcount += 1;
+
+ return set;
+}
+
+void desktop_entry_set_unref(DesktopEntrySet* set)
+{
+ g_return_if_fail (set != NULL);
+ g_return_if_fail (set->refcount > 0);
+
+ set->refcount -= 1;
+ if (set->refcount == 0)
+ {
+ menu_verbose (" Deleting entry set %p\n", set);
+
+ if (set->hash)
+ g_hash_table_destroy (set->hash);
+ set->hash = NULL;
+
+ g_free (set);
+ }
+}
+
+void desktop_entry_set_add_entry(DesktopEntrySet* set, DesktopEntry* entry, const char* file_id)
+{
+ menu_verbose (" Adding to set %p entry %s\n", set, file_id);
+
+ if (set->hash == NULL)
+ {
+ set->hash = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) desktop_entry_unref);
+ }
+
+ g_hash_table_replace (set->hash,
+ g_strdup (file_id),
+ desktop_entry_ref (entry));
+}
+
+DesktopEntry* desktop_entry_set_lookup(DesktopEntrySet* set, const char* file_id)
+{
+ if (set->hash == NULL)
+ return NULL;
+
+ return g_hash_table_lookup (set->hash, file_id);
+}
+
+typedef struct {
+ DesktopEntrySetForeachFunc func;
+ gpointer user_data;
+} EntryHashForeachData;
+
+static void entry_hash_foreach(const char* file_id, DesktopEntry* entry, EntryHashForeachData* fd)
+{
+ fd->func(file_id, entry, fd->user_data);
+}
+
+void desktop_entry_set_foreach(DesktopEntrySet* set, DesktopEntrySetForeachFunc func, gpointer user_data)
+{
+ g_return_if_fail (set != NULL);
+ g_return_if_fail (func != NULL);
+
+ if (set->hash != NULL)
+ {
+ EntryHashForeachData fd;
+
+ fd.func = func;
+ fd.user_data = user_data;
+
+ g_hash_table_foreach (set->hash,
+ (GHFunc) entry_hash_foreach,
+ &fd);
+ }
+}
+
+static void desktop_entry_set_clear(DesktopEntrySet* set)
+{
+ menu_verbose (" Clearing set %p\n", set);
+
+ if (set->hash != NULL)
+ {
+ g_hash_table_destroy (set->hash);
+ set->hash = NULL;
+ }
+}
+
+int desktop_entry_set_get_count(DesktopEntrySet* set)
+{
+ if (set->hash == NULL)
+ return 0;
+
+ return g_hash_table_size (set->hash);
+}
+
+static void union_foreach(const char* file_id, DesktopEntry* entry, DesktopEntrySet* set)
+{
+ /* we are iterating over "with" adding anything not
+ * already in "set". We unconditionally overwrite
+ * the stuff in "set" because we can assume
+ * two entries with the same name are equivalent.
+ */
+ desktop_entry_set_add_entry(set, entry, file_id);
+}
+
+void desktop_entry_set_union(DesktopEntrySet* set, DesktopEntrySet* with)
+{
+ menu_verbose (" Union of %p and %p\n", set, with);
+
+ if (desktop_entry_set_get_count (with) == 0)
+ return; /* A fast simple case */
+
+ g_hash_table_foreach (with->hash,
+ (GHFunc) union_foreach,
+ set);
+}
+
+typedef struct {
+ DesktopEntrySet *set;
+ DesktopEntrySet *with;
+} IntersectData;
+
+static gboolean intersect_foreach_remove(const char* file_id, DesktopEntry* entry, IntersectData* id)
+{
+ /* Remove everything in "set" which is not in "with" */
+
+ if (g_hash_table_lookup (id->with->hash, file_id) != NULL)
+ return FALSE;
+
+ menu_verbose (" Removing from %p entry %s\n", id->set, file_id);
+
+ return TRUE; /* return TRUE to remove */
+}
+
+void desktop_entry_set_intersection(DesktopEntrySet* set, DesktopEntrySet* with)
+{
+ IntersectData id;
+
+ menu_verbose (" Intersection of %p and %p\n", set, with);
+
+ if (desktop_entry_set_get_count (set) == 0 ||
+ desktop_entry_set_get_count (with) == 0)
+ {
+ /* A fast simple case */
+ desktop_entry_set_clear (set);
+ return;
+ }
+
+ id.set = set;
+ id.with = with;
+
+ g_hash_table_foreach_remove (set->hash,
+ (GHRFunc) intersect_foreach_remove,
+ &id);
+}
+
+typedef struct {
+ DesktopEntrySet *set;
+ DesktopEntrySet *other;
+} SubtractData;
+
+static gboolean subtract_foreach_remove(const char* file_id, DesktopEntry* entry, SubtractData* sd)
+{
+ /* Remove everything in "set" which is not in "other" */
+
+ if (g_hash_table_lookup (sd->other->hash, file_id) == NULL)
+ return FALSE;
+
+ menu_verbose (" Removing from %p entry %s\n", sd->set, file_id);
+
+ return TRUE; /* return TRUE to remove */
+}
+
+void desktop_entry_set_subtract(DesktopEntrySet* set, DesktopEntrySet* other)
+{
+ SubtractData sd;
+
+ menu_verbose (" Subtract from %p set %p\n", set, other);
+
+ if (desktop_entry_set_get_count (set) == 0 ||
+ desktop_entry_set_get_count (other) == 0)
+ return; /* A fast simple case */
+
+ sd.set = set;
+ sd.other = other;
+
+ g_hash_table_foreach_remove (set->hash,
+ (GHRFunc) subtract_foreach_remove,
+ &sd);
+}
+
+void desktop_entry_set_swap_contents(DesktopEntrySet* a, DesktopEntrySet* b)
+{
+ GHashTable *tmp;
+
+ menu_verbose (" Swap contents of %p and %p\n", a, b);
+
+ tmp = a->hash;
+ a->hash = b->hash;
+ b->hash = tmp;
+}
diff --git a/libmenu/desktop-entries.h b/libmenu/desktop-entries.h
new file mode 100644
index 0000000..19d9abd
--- /dev/null
+++ b/libmenu/desktop-entries.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2002 - 2004 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __DESKTOP_ENTRIES_H__
+#define __DESKTOP_ENTRIES_H__
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ DESKTOP_ENTRY_INVALID = 0,
+ DESKTOP_ENTRY_DESKTOP,
+ DESKTOP_ENTRY_DIRECTORY
+} DesktopEntryType;
+
+typedef struct DesktopEntry DesktopEntry;
+
+DesktopEntry* desktop_entry_new(const char* path);
+
+DesktopEntry* desktop_entry_ref(DesktopEntry* entry);
+DesktopEntry* desktop_entry_copy(DesktopEntry* entry);
+DesktopEntry* desktop_entry_reload(DesktopEntry* entry);
+void desktop_entry_unref(DesktopEntry* entry);
+
+DesktopEntryType desktop_entry_get_type(DesktopEntry* entry);
+const char* desktop_entry_get_path(DesktopEntry* entry);
+const char* desktop_entry_get_basename(DesktopEntry* entry);
+
+const char* desktop_entry_get_name(DesktopEntry* entry);
+const char* desktop_entry_get_generic_name(DesktopEntry* entry);
+const char* desktop_entry_get_full_name(DesktopEntry* entry);
+const char* desktop_entry_get_comment(DesktopEntry* entry);
+const char* desktop_entry_get_icon(DesktopEntry* entry);
+const char* desktop_entry_get_exec(DesktopEntry* entry);
+gboolean desktop_entry_get_launch_in_terminal(DesktopEntry* entry);
+
+gboolean desktop_entry_get_hidden(DesktopEntry* entry);
+gboolean desktop_entry_get_no_display(DesktopEntry* entry);
+gboolean desktop_entry_get_show_in_mate(DesktopEntry* entry);
+gboolean desktop_entry_get_tryexec_failed(DesktopEntry* entry);
+
+gboolean desktop_entry_has_categories(DesktopEntry* entry);
+gboolean desktop_entry_has_category(DesktopEntry* entry, const char* category);
+
+void desktop_entry_add_legacy_category(DesktopEntry* src);
+
+
+typedef struct DesktopEntrySet DesktopEntrySet;
+
+DesktopEntrySet* desktop_entry_set_new(void);
+DesktopEntrySet* desktop_entry_set_ref(DesktopEntrySet* set);
+void desktop_entry_set_unref(DesktopEntrySet* set);
+
+void desktop_entry_set_add_entry(DesktopEntrySet* set, DesktopEntry* entry, const char* file_id);
+DesktopEntry* desktop_entry_set_lookup(DesktopEntrySet* set, const char* file_id);
+int desktop_entry_set_get_count(DesktopEntrySet* set);
+
+void desktop_entry_set_union(DesktopEntrySet* set, DesktopEntrySet* with);
+void desktop_entry_set_intersection(DesktopEntrySet* set, DesktopEntrySet* with);
+void desktop_entry_set_subtract(DesktopEntrySet* set, DesktopEntrySet* other);
+void desktop_entry_set_swap_contents(DesktopEntrySet* a, DesktopEntrySet* b);
+
+typedef void (*DesktopEntrySetForeachFunc) (const char* file_id, DesktopEntry* entry, gpointer user_data);
+
+void desktop_entry_set_foreach(DesktopEntrySet* set, DesktopEntrySetForeachFunc func, gpointer user_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __DESKTOP_ENTRIES_H__ */
diff --git a/libmenu/entry-directories.c b/libmenu/entry-directories.c
new file mode 100644
index 0000000..a442a65
--- /dev/null
+++ b/libmenu/entry-directories.c
@@ -0,0 +1,1105 @@
+/*
+ * Copyright (C) 2002 - 2004 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include "entry-directories.h"
+
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "menu-util.h"
+#include "menu-monitor.h"
+#include "canonicalize.h"
+
+typedef struct CachedDir CachedDir;
+typedef struct CachedDirMonitor CachedDirMonitor;
+
+struct EntryDirectory {
+ CachedDir* dir;
+ char* legacy_prefix;
+
+ guint entry_type: 2;
+ guint is_legacy: 1;
+ guint refcount: 24;
+};
+
+struct EntryDirectoryList {
+ int refcount;
+ int length;
+ GList* dirs;
+};
+
+struct CachedDir {
+ CachedDir* parent;
+ char* name;
+
+ GSList* entries;
+ GSList* subdirs;
+
+ MenuMonitor* dir_monitor;
+ GSList* monitors;
+
+ guint have_read_entries: 1;
+ guint deleted: 1;
+
+ guint references: 28;
+};
+
+struct CachedDirMonitor {
+ EntryDirectory* ed;
+ EntryDirectoryChangedFunc callback;
+ gpointer user_data;
+};
+
+static void cached_dir_free(CachedDir* dir);
+static gboolean cached_dir_load_entries_recursive(CachedDir* dir, const char* dirname);
+
+static void handle_cached_dir_changed(MenuMonitor* monitor, MenuMonitorEvent event, const char* path, CachedDir* dir);
+
+/*
+ * Entry directory cache
+ */
+
+static CachedDir* dir_cache = NULL;
+
+static CachedDir* cached_dir_new(const char *name)
+{
+ CachedDir* dir;
+
+ dir = g_new0(CachedDir, 1);
+
+ dir->name = g_strdup(name);
+
+ return dir;
+}
+
+static void cached_dir_free(CachedDir* dir)
+{
+ if (dir->dir_monitor)
+ {
+ menu_monitor_remove_notify (dir->dir_monitor,
+ (MenuMonitorNotifyFunc) handle_cached_dir_changed,
+ dir);
+ menu_monitor_unref (dir->dir_monitor);
+ dir->dir_monitor = NULL;
+ }
+
+ g_slist_foreach (dir->monitors, (GFunc) g_free, NULL);
+ g_slist_free (dir->monitors);
+ dir->monitors = NULL;
+
+ g_slist_foreach (dir->entries,
+ (GFunc) desktop_entry_unref,
+ NULL);
+ g_slist_free (dir->entries);
+ dir->entries = NULL;
+
+ g_slist_foreach (dir->subdirs,
+ (GFunc) cached_dir_free,
+ NULL);
+ g_slist_free (dir->subdirs);
+ dir->subdirs = NULL;
+
+ g_free (dir->name);
+ g_free (dir);
+}
+
+static inline CachedDir* find_subdir(CachedDir* dir, const char* subdir)
+{
+ GSList *tmp;
+
+ tmp = dir->subdirs;
+ while (tmp != NULL)
+ {
+ CachedDir *sub = tmp->data;
+
+ if (strcmp (sub->name, subdir) == 0)
+ return sub;
+
+ tmp = tmp->next;
+ }
+
+ return NULL;
+}
+
+static DesktopEntry* find_entry(CachedDir* dir, const char* basename)
+{
+ GSList *tmp;
+
+ tmp = dir->entries;
+ while (tmp != NULL)
+ {
+ if (strcmp (desktop_entry_get_basename (tmp->data), basename) == 0)
+ return tmp->data;
+
+ tmp = tmp->next;
+ }
+
+ return NULL;
+}
+
+static DesktopEntry* cached_dir_find_relative_path(CachedDir* dir, const char* relative_path)
+{
+ DesktopEntry *retval = NULL;
+ char **split;
+ int i;
+
+ split = g_strsplit (relative_path, "/", -1);
+
+ i = 0;
+ while (split[i] != NULL)
+ {
+ if (split[i + 1] != NULL)
+ {
+ if ((dir = find_subdir (dir, split[i])) == NULL)
+ break;
+ }
+ else
+ {
+ retval = find_entry (dir, split[i]);
+ break;
+ }
+
+ ++i;
+ }
+
+ g_strfreev (split);
+
+ return retval;
+}
+
+static CachedDir* cached_dir_lookup(const char* canonical)
+{
+ CachedDir *dir;
+ char **split;
+ int i;
+
+ if (dir_cache == NULL)
+ dir_cache = cached_dir_new ("/");
+ dir = dir_cache;
+
+ g_assert (canonical != NULL && canonical[0] == G_DIR_SEPARATOR);
+
+ menu_verbose ("Looking up cached dir \"%s\"\n", canonical);
+
+ split = g_strsplit (canonical + 1, "/", -1);
+
+ i = 0;
+ while (split[i] != NULL)
+ {
+ CachedDir *subdir;
+
+ if ((subdir = find_subdir (dir, split[i])) == NULL)
+ {
+ subdir = cached_dir_new (split[i]);
+ dir->subdirs = g_slist_prepend (dir->subdirs, subdir);
+ subdir->parent = dir;
+ }
+
+ dir = subdir;
+
+ ++i;
+ }
+
+ g_strfreev (split);
+
+ g_assert (dir != NULL);
+
+ return dir;
+}
+
+static gboolean cached_dir_add_entry(CachedDir* dir, const char* basename, const char* path)
+{
+ DesktopEntry *entry;
+
+ entry = desktop_entry_new (path);
+ if (entry == NULL)
+ return FALSE;
+
+ dir->entries = g_slist_prepend (dir->entries, entry);
+
+ return TRUE;
+}
+
+static gboolean cached_dir_update_entry(CachedDir* dir, const char* basename, const char* path)
+{
+ GSList *tmp;
+
+ tmp = dir->entries;
+ while (tmp != NULL)
+ {
+ if (strcmp (desktop_entry_get_basename (tmp->data), basename) == 0)
+ {
+ if (!desktop_entry_reload (tmp->data))
+ {
+ dir->entries = g_slist_delete_link (dir->entries, tmp);
+ }
+
+ return TRUE;
+ }
+
+ tmp = tmp->next;
+ }
+
+ return cached_dir_add_entry (dir, basename, path);
+}
+
+static gboolean cached_dir_remove_entry(CachedDir* dir, const char* basename)
+{
+ GSList *tmp;
+
+ tmp = dir->entries;
+ while (tmp != NULL)
+ {
+ if (strcmp (desktop_entry_get_basename (tmp->data), basename) == 0)
+ {
+ desktop_entry_unref (tmp->data);
+ dir->entries = g_slist_delete_link (dir->entries, tmp);
+ return TRUE;
+ }
+
+ tmp = tmp->next;
+ }
+
+ return FALSE;
+}
+
+static gboolean cached_dir_add_subdir(CachedDir* dir, const char* basename, const char* path)
+{
+ CachedDir *subdir;
+
+ subdir = find_subdir (dir, basename);
+
+ if (subdir != NULL)
+ {
+ subdir->deleted = FALSE;
+ return TRUE;
+ }
+
+ subdir = cached_dir_new (basename);
+
+ if (!cached_dir_load_entries_recursive (subdir, path))
+ {
+ cached_dir_free (subdir);
+ return FALSE;
+ }
+
+ menu_verbose ("Caching dir \"%s\"\n", basename);
+
+ subdir->parent = dir;
+ dir->subdirs = g_slist_prepend (dir->subdirs, subdir);
+
+ return TRUE;
+}
+
+static gboolean cached_dir_remove_subdir(CachedDir* dir, const char* basename)
+{
+ CachedDir *subdir;
+
+ subdir = find_subdir (dir, basename);
+
+ if (subdir != NULL)
+ {
+ subdir->deleted = TRUE;
+
+ if (subdir->references == 0)
+ {
+ cached_dir_free (subdir);
+ dir->subdirs = g_slist_remove (dir->subdirs, subdir);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void cached_dir_invoke_monitors(CachedDir* dir)
+{
+ GSList *tmp;
+
+ tmp = dir->monitors;
+ while (tmp != NULL)
+ {
+ CachedDirMonitor *monitor = tmp->data;
+ GSList *next = tmp->next;
+
+ monitor->callback (monitor->ed, monitor->user_data);
+
+ tmp = next;
+ }
+
+ if (dir->parent)
+ {
+ cached_dir_invoke_monitors (dir->parent);
+ }
+}
+
+static void handle_cached_dir_changed (MenuMonitor* monitor, MenuMonitorEvent event, const char* path, CachedDir* dir)
+{
+ gboolean handled = FALSE;
+ char *basename;
+ char *dirname;
+
+ menu_verbose ("'%s' notified of '%s' %s - invalidating cache\n",
+ dir->name,
+ path,
+ event == MENU_MONITOR_EVENT_CREATED ? ("created") :
+ event == MENU_MONITOR_EVENT_DELETED ? ("deleted") : ("changed"));
+
+ dirname = g_path_get_dirname (path);
+ basename = g_path_get_basename (path);
+
+ dir = cached_dir_lookup (dirname);
+
+ if (g_str_has_suffix (basename, ".desktop") ||
+ g_str_has_suffix (basename, ".directory"))
+ {
+ switch (event)
+ {
+ case MENU_MONITOR_EVENT_CREATED:
+ case MENU_MONITOR_EVENT_CHANGED:
+ handled = cached_dir_update_entry (dir, basename, path);
+ break;
+
+ case MENU_MONITOR_EVENT_DELETED:
+ handled = cached_dir_remove_entry (dir, basename);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+ else /* Try recursing */
+ {
+ switch (event)
+ {
+ case MENU_MONITOR_EVENT_CREATED:
+ handled = cached_dir_add_subdir (dir, basename, path);
+ break;
+
+ case MENU_MONITOR_EVENT_CHANGED:
+ break;
+
+ case MENU_MONITOR_EVENT_DELETED:
+ handled = cached_dir_remove_subdir (dir, basename);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+ g_free (basename);
+ g_free (dirname);
+
+ if (handled)
+ {
+ /* CHANGED events don't change the set of desktop entries */
+ if (event == MENU_MONITOR_EVENT_CREATED || event == MENU_MONITOR_EVENT_DELETED)
+ {
+ _entry_directory_list_empty_desktop_cache ();
+ }
+
+ cached_dir_invoke_monitors (dir);
+ }
+}
+
+static void cached_dir_ensure_monitor(CachedDir* dir, const char* dirname)
+{
+ if (dir->dir_monitor == NULL)
+ {
+ dir->dir_monitor = menu_get_directory_monitor (dirname);
+ menu_monitor_add_notify (dir->dir_monitor,
+ (MenuMonitorNotifyFunc) handle_cached_dir_changed,
+ dir);
+ }
+}
+
+static gboolean cached_dir_load_entries_recursive(CachedDir* dir, const char* dirname)
+{
+ DIR *dp;
+ struct dirent *dent;
+ GString *fullpath;
+ gsize fullpath_len;
+
+ g_assert (dir != NULL);
+
+ if (dir->have_read_entries)
+ return TRUE;
+
+ menu_verbose ("Attempting to read entries from %s (full path %s)\n",
+ dir->name, dirname);
+
+ dp = opendir (dirname);
+ if (dp == NULL)
+ {
+ menu_verbose ("Unable to list directory \"%s\"\n",
+ dirname);
+ return FALSE;
+ }
+
+ cached_dir_ensure_monitor (dir, dirname);
+
+ fullpath = g_string_new (dirname);
+ if (fullpath->str[fullpath->len - 1] != G_DIR_SEPARATOR)
+ g_string_append_c (fullpath, G_DIR_SEPARATOR);
+
+ fullpath_len = fullpath->len;
+
+ while ((dent = readdir (dp)) != NULL)
+ {
+ /* ignore . and .. */
+ if (dent->d_name[0] == '.' &&
+ (dent->d_name[1] == '\0' ||
+ (dent->d_name[1] == '.' &&
+ dent->d_name[2] == '\0')))
+ continue;
+
+ g_string_append (fullpath, dent->d_name);
+
+ if (g_str_has_suffix (dent->d_name, ".desktop") ||
+ g_str_has_suffix (dent->d_name, ".directory"))
+ {
+ cached_dir_add_entry (dir, dent->d_name, fullpath->str);
+ }
+ else /* Try recursing */
+ {
+ cached_dir_add_subdir (dir, dent->d_name, fullpath->str);
+ }
+
+ g_string_truncate (fullpath, fullpath_len);
+ }
+
+ closedir (dp);
+
+ g_string_free (fullpath, TRUE);
+
+ dir->have_read_entries = TRUE;
+
+ return TRUE;
+}
+
+static void cached_dir_add_monitor(CachedDir* dir, EntryDirectory* ed, EntryDirectoryChangedFunc callback, gpointer user_data)
+{
+ CachedDirMonitor *monitor;
+ GSList *tmp;
+
+ tmp = dir->monitors;
+ while (tmp != NULL)
+ {
+ monitor = tmp->data;
+
+ if (monitor->ed == ed &&
+ monitor->callback == callback &&
+ monitor->user_data == user_data)
+ break;
+
+ tmp = tmp->next;
+ }
+
+ if (tmp == NULL)
+ {
+ monitor = g_new0 (CachedDirMonitor, 1);
+ monitor->ed = ed;
+ monitor->callback = callback;
+ monitor->user_data = user_data;
+
+ dir->monitors = g_slist_append (dir->monitors, monitor);
+ }
+}
+
+static void cached_dir_remove_monitor(CachedDir* dir, EntryDirectory* ed, EntryDirectoryChangedFunc callback, gpointer user_data)
+{
+ GSList *tmp;
+
+ tmp = dir->monitors;
+ while (tmp != NULL)
+ {
+ CachedDirMonitor *monitor = tmp->data;
+ GSList *next = tmp->next;
+
+ if (monitor->ed == ed &&
+ monitor->callback == callback &&
+ monitor->user_data == user_data)
+ {
+ dir->monitors = g_slist_delete_link (dir->monitors, tmp);
+ g_free (monitor);
+ }
+
+ tmp = next;
+ }
+}
+
+static void cached_dir_add_reference(CachedDir* dir)
+{
+ dir->references++;
+
+ if (dir->parent != NULL)
+ {
+ cached_dir_add_reference (dir->parent);
+ }
+}
+
+static void cached_dir_remove_reference(CachedDir* dir)
+{
+ CachedDir *parent;
+
+ parent = dir->parent;
+
+ if (--dir->references == 0 && dir->deleted)
+ {
+ if (dir->parent != NULL)
+ {
+ GSList *tmp;
+
+ tmp = parent->subdirs;
+ while (tmp != NULL)
+ {
+ CachedDir *subdir = tmp->data;
+
+ if (!strcmp (subdir->name, dir->name))
+ {
+ parent->subdirs = g_slist_delete_link (parent->subdirs, tmp);
+ break;
+ }
+
+ tmp = tmp->next;
+ }
+ }
+
+ cached_dir_free (dir);
+ }
+
+ if (parent != NULL)
+ {
+ cached_dir_remove_reference (parent);
+ }
+}
+
+/*
+ * Entry directories
+ */
+
+static EntryDirectory* entry_directory_new_full(DesktopEntryType entry_type, const char* path, gboolean is_legacy, const char* legacy_prefix)
+{
+ EntryDirectory *ed;
+ char *canonical;
+
+ menu_verbose ("Loading entry directory \"%s\" (legacy %s)\n",
+ path,
+ is_legacy ? "<yes>" : "<no>");
+
+ canonical = menu_canonicalize_file_name (path, FALSE);
+ if (canonical == NULL)
+ {
+ menu_verbose ("Failed to canonicalize \"%s\": %s\n",
+ path, g_strerror (errno));
+ return NULL;
+ }
+
+ ed = g_new0 (EntryDirectory, 1);
+
+ ed->dir = cached_dir_lookup (canonical);
+ g_assert (ed->dir != NULL);
+
+ cached_dir_add_reference (ed->dir);
+ cached_dir_load_entries_recursive (ed->dir, canonical);
+
+ ed->legacy_prefix = g_strdup (legacy_prefix);
+ ed->entry_type = entry_type;
+ ed->is_legacy = is_legacy != FALSE;
+ ed->refcount = 1;
+
+ g_free (canonical);
+
+ return ed;
+}
+
+EntryDirectory* entry_directory_new(DesktopEntryType entry_type, const char* path)
+{
+ return entry_directory_new_full (entry_type, path, FALSE, NULL);
+}
+
+EntryDirectory* entry_directory_new_legacy(DesktopEntryType entry_type, const char* path, const char* legacy_prefix)
+{
+ return entry_directory_new_full(entry_type, path, TRUE, legacy_prefix);
+}
+
+EntryDirectory* entry_directory_ref(EntryDirectory* ed)
+{
+ g_return_val_if_fail(ed != NULL, NULL);
+ g_return_val_if_fail(ed->refcount > 0, NULL);
+
+ ed->refcount++;
+
+ return ed;
+}
+
+void entry_directory_unref(EntryDirectory* ed)
+{
+ g_return_if_fail (ed != NULL);
+ g_return_if_fail (ed->refcount > 0);
+
+ if (--ed->refcount == 0)
+ {
+ cached_dir_remove_reference (ed->dir);
+
+ ed->dir = NULL;
+ ed->entry_type = DESKTOP_ENTRY_INVALID;
+ ed->is_legacy = FALSE;
+
+ g_free (ed->legacy_prefix);
+ ed->legacy_prefix = NULL;
+
+ g_free (ed);
+ }
+}
+
+static void entry_directory_add_monitor(EntryDirectory* ed, EntryDirectoryChangedFunc callback, gpointer user_data)
+{
+ cached_dir_add_monitor (ed->dir, ed, callback, user_data);
+}
+
+static void entry_directory_remove_monitor(EntryDirectory* ed, EntryDirectoryChangedFunc callback, gpointer user_data)
+{
+ cached_dir_remove_monitor (ed->dir, ed, callback, user_data);
+}
+
+static DesktopEntry* entry_directory_get_directory(EntryDirectory* ed, const char* relative_path)
+{
+ DesktopEntry *entry;
+
+ if (ed->entry_type != DESKTOP_ENTRY_DIRECTORY)
+ return NULL;
+
+ entry = cached_dir_find_relative_path (ed->dir, relative_path);
+ if (entry == NULL || desktop_entry_get_type (entry) != DESKTOP_ENTRY_DIRECTORY)
+ return NULL;
+
+ return desktop_entry_ref (entry);
+}
+
+static char* get_desktop_file_id_from_path(EntryDirectory* ed, DesktopEntryType entry_type, const char* relative_path)
+{
+ char *retval;
+
+ retval = NULL;
+
+ if (entry_type == DESKTOP_ENTRY_DESKTOP)
+ {
+ if (!ed->is_legacy)
+ {
+ retval = g_strdelimit (g_strdup (relative_path), "/", '-');
+ }
+ else
+ {
+ char *basename;
+
+ basename = g_path_get_basename (relative_path);
+
+ if (ed->legacy_prefix)
+ {
+ retval = g_strjoin ("-", ed->legacy_prefix, basename, NULL);
+ g_free (basename);
+ }
+ else
+ {
+ retval = basename;
+ }
+ }
+ }
+ else
+ {
+ retval = g_strdup (relative_path);
+ }
+
+ return retval;
+}
+
+typedef gboolean (*EntryDirectoryForeachFunc) (EntryDirectory* ed, DesktopEntry* entry, const char* file_id, DesktopEntrySet* set, gpointer user_data);
+
+static gboolean entry_directory_foreach_recursive(EntryDirectory* ed, CachedDir* cd, GString* relative_path, EntryDirectoryForeachFunc func, DesktopEntrySet* set, gpointer user_data)
+{
+ GSList *tmp;
+ int relative_path_len;
+
+ if (cd->deleted)
+ return TRUE;
+
+ relative_path_len = relative_path->len;
+
+ tmp = cd->entries;
+ while (tmp != NULL)
+ {
+ DesktopEntry *entry = tmp->data;
+
+ if (desktop_entry_get_type (entry) == ed->entry_type)
+ {
+ gboolean ret;
+ char *file_id;
+
+ g_string_append (relative_path,
+ desktop_entry_get_basename (entry));
+
+ file_id = get_desktop_file_id_from_path (ed,
+ ed->entry_type,
+ relative_path->str);
+
+ ret = func (ed, entry, file_id, set, user_data);
+
+ g_free (file_id);
+
+ g_string_truncate (relative_path, relative_path_len);
+
+ if (!ret)
+ return FALSE;
+ }
+
+ tmp = tmp->next;
+ }
+
+ tmp = cd->subdirs;
+ while (tmp != NULL)
+ {
+ CachedDir *subdir = tmp->data;
+
+ g_string_append (relative_path, subdir->name);
+ g_string_append_c (relative_path, G_DIR_SEPARATOR);
+
+ if (!entry_directory_foreach_recursive (ed,
+ subdir,
+ relative_path,
+ func,
+ set,
+ user_data))
+ return FALSE;
+
+ g_string_truncate (relative_path, relative_path_len);
+
+ tmp = tmp->next;
+ }
+
+ return TRUE;
+}
+
+static void entry_directory_foreach(EntryDirectory* ed, EntryDirectoryForeachFunc func, DesktopEntrySet* set, gpointer user_data)
+{
+ GString *path;
+
+ path = g_string_new (NULL);
+
+ entry_directory_foreach_recursive (ed,
+ ed->dir,
+ path,
+ func,
+ set,
+ user_data);
+
+ g_string_free (path, TRUE);
+}
+
+void entry_directory_get_flat_contents(EntryDirectory* ed, DesktopEntrySet* desktop_entries, DesktopEntrySet* directory_entries, GSList** subdirs)
+{
+ GSList *tmp;
+
+ if (subdirs)
+ *subdirs = NULL;
+
+ tmp = ed->dir->entries;
+ while (tmp != NULL)
+ {
+ DesktopEntry *entry = tmp->data;
+ const char *basename;
+
+ basename = desktop_entry_get_basename (entry);
+
+ if (desktop_entries &&
+ desktop_entry_get_type (entry) == DESKTOP_ENTRY_DESKTOP)
+ {
+ char *file_id;
+
+ file_id = get_desktop_file_id_from_path (ed,
+ DESKTOP_ENTRY_DESKTOP,
+ basename);
+
+ desktop_entry_set_add_entry (desktop_entries,
+ entry,
+ file_id);
+
+ g_free (file_id);
+ }
+
+ if (directory_entries &&
+ desktop_entry_get_type (entry) == DESKTOP_ENTRY_DIRECTORY)
+ {
+ desktop_entry_set_add_entry (directory_entries,
+ entry,
+ basename);
+ }
+
+ tmp = tmp->next;
+ }
+
+ if (subdirs)
+ {
+ tmp = ed->dir->subdirs;
+ while (tmp != NULL)
+ {
+ CachedDir *cd = tmp->data;
+
+ if (!cd->deleted)
+ {
+ *subdirs = g_slist_prepend (*subdirs, g_strdup (cd->name));
+ }
+
+ tmp = tmp->next;
+ }
+ }
+
+ if (subdirs)
+ *subdirs = g_slist_reverse (*subdirs);
+}
+
+/*
+ * Entry directory lists
+ */
+
+EntryDirectoryList* entry_directory_list_new(void)
+{
+ EntryDirectoryList *list;
+
+ list = g_new0 (EntryDirectoryList, 1);
+
+ list->refcount = 1;
+ list->dirs = NULL;
+ list->length = 0;
+
+ return list;
+}
+
+EntryDirectoryList* entry_directory_list_ref(EntryDirectoryList* list)
+{
+ g_return_val_if_fail (list != NULL, NULL);
+ g_return_val_if_fail (list->refcount > 0, NULL);
+
+ list->refcount += 1;
+
+ return list;
+}
+
+void entry_directory_list_unref(EntryDirectoryList* list)
+{
+ g_return_if_fail (list != NULL);
+ g_return_if_fail (list->refcount > 0);
+
+ list->refcount -= 1;
+ if (list->refcount == 0)
+ {
+ g_list_foreach (list->dirs, (GFunc) entry_directory_unref, NULL);
+ g_list_free (list->dirs);
+ list->dirs = NULL;
+ list->length = 0;
+ g_free (list);
+ }
+}
+
+void entry_directory_list_prepend(EntryDirectoryList* list, EntryDirectory* ed)
+{
+ list->length += 1;
+ list->dirs = g_list_prepend (list->dirs,
+ entry_directory_ref (ed));
+}
+
+int entry_directory_list_get_length(EntryDirectoryList* list)
+{
+ return list->length;
+}
+
+void entry_directory_list_append_list(EntryDirectoryList* list, EntryDirectoryList* to_append)
+{
+ GList *tmp;
+ GList *new_dirs = NULL;
+
+ if (to_append->length == 0)
+ return;
+
+ tmp = to_append->dirs;
+ while (tmp != NULL)
+ {
+ list->length += 1;
+ new_dirs = g_list_prepend (new_dirs,
+ entry_directory_ref (tmp->data));
+
+ tmp = tmp->next;
+ }
+
+ new_dirs = g_list_reverse (new_dirs);
+ list->dirs = g_list_concat (list->dirs, new_dirs);
+}
+
+DesktopEntry* entry_directory_list_get_directory(EntryDirectoryList *list, const char* relative_path)
+{
+ DesktopEntry *retval = NULL;
+ GList *tmp;
+
+ tmp = list->dirs;
+ while (tmp != NULL)
+ {
+ if ((retval = entry_directory_get_directory (tmp->data, relative_path)) != NULL)
+ break;
+
+ tmp = tmp->next;
+ }
+
+ return retval;
+}
+
+gboolean _entry_directory_list_compare(const EntryDirectoryList* a, const EntryDirectoryList* b)
+{
+ GList *al, *bl;
+
+ if (a == NULL && b == NULL)
+ return TRUE;
+
+ if ((a == NULL || b == NULL))
+ return FALSE;
+
+ if (a->length != b->length)
+ return FALSE;
+
+ al = a->dirs; bl = b->dirs;
+ while (al && bl && al->data == bl->data)
+ {
+ al = al->next;
+ bl = bl->next;
+ }
+
+ return (al == NULL && bl == NULL);
+}
+
+static gboolean get_all_func(EntryDirectory* ed, DesktopEntry* entry, const char* file_id, DesktopEntrySet* set, gpointer user_data)
+{
+ if (ed->is_legacy && !desktop_entry_has_categories (entry))
+ {
+ entry = desktop_entry_copy (entry);
+ desktop_entry_add_legacy_category (entry);
+ }
+ else
+ {
+ entry = desktop_entry_ref (entry);
+ }
+
+ desktop_entry_set_add_entry (set, entry, file_id);
+ desktop_entry_unref (entry);
+
+ return TRUE;
+}
+
+static DesktopEntrySet* entry_directory_last_set = NULL;
+static EntryDirectoryList* entry_directory_last_list = NULL;
+
+void _entry_directory_list_empty_desktop_cache(void)
+{
+ if (entry_directory_last_set != NULL)
+ desktop_entry_set_unref (entry_directory_last_set);
+ entry_directory_last_set = NULL;
+
+ if (entry_directory_last_list != NULL)
+ entry_directory_list_unref (entry_directory_last_list);
+ entry_directory_last_list = NULL;
+}
+
+DesktopEntrySet* _entry_directory_list_get_all_desktops(EntryDirectoryList* list)
+{
+ GList *tmp;
+ DesktopEntrySet *set;
+
+ /* The only tricky thing here is that desktop files later
+ * in the search list with the same relative path
+ * are "hidden" by desktop files earlier in the path,
+ * so we have to do the earlier files first causing
+ * the later files to replace the earlier files
+ * in the DesktopEntrySet
+ *
+ * We go from the end of the list so we can just
+ * g_hash_table_replace and not have to do two
+ * hash lookups (check for existing entry, then insert new
+ * entry)
+ */
+
+ /* This method is -extremely- slow, so we have a simple
+ one-entry cache here */
+ if (_entry_directory_list_compare (list, entry_directory_last_list))
+ {
+ menu_verbose (" Hit desktop list (%p) cache\n", list);
+ return desktop_entry_set_ref (entry_directory_last_set);
+ }
+
+ if (entry_directory_last_set != NULL)
+ desktop_entry_set_unref (entry_directory_last_set);
+ if (entry_directory_last_list != NULL)
+ entry_directory_list_unref (entry_directory_last_list);
+
+ set = desktop_entry_set_new ();
+ menu_verbose (" Storing all of list %p in set %p\n",
+ list, set);
+
+ tmp = g_list_last (list->dirs);
+ while (tmp != NULL)
+ {
+ entry_directory_foreach (tmp->data, get_all_func, set, NULL);
+
+ tmp = tmp->prev;
+ }
+
+ entry_directory_last_list = entry_directory_list_ref (list);
+ entry_directory_last_set = desktop_entry_set_ref (set);
+
+ return set;
+}
+
+void entry_directory_list_add_monitors(EntryDirectoryList* list, EntryDirectoryChangedFunc callback, gpointer user_data)
+{
+ GList *tmp;
+
+ tmp = list->dirs;
+ while (tmp != NULL)
+ {
+ entry_directory_add_monitor (tmp->data, callback, user_data);
+ tmp = tmp->next;
+ }
+}
+
+void entry_directory_list_remove_monitors(EntryDirectoryList* list, EntryDirectoryChangedFunc callback, gpointer user_data)
+{
+ GList *tmp;
+
+ tmp = list->dirs;
+ while (tmp != NULL)
+ {
+ entry_directory_remove_monitor (tmp->data, callback, user_data);
+ tmp = tmp->next;
+ }
+}
diff --git a/libmenu/entry-directories.h b/libmenu/entry-directories.h
new file mode 100644
index 0000000..3cc4d9b
--- /dev/null
+++ b/libmenu/entry-directories.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2002 - 2004 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __ENTRY_DIRECTORIES_H__
+#define __ENTRY_DIRECTORIES_H__
+
+#include <glib.h>
+#include "desktop-entries.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct EntryDirectory EntryDirectory;
+
+typedef void (*EntryDirectoryChangedFunc) (EntryDirectory* ed, gpointer user_data);
+
+EntryDirectory* entry_directory_new(DesktopEntryType entry_type, const char* path);
+EntryDirectory* entry_directory_new_legacy(DesktopEntryType entry_type, const char* path, const char* legacy_prefix);
+
+EntryDirectory* entry_directory_ref(EntryDirectory* ed);
+void entry_directory_unref(EntryDirectory* ed);
+
+void entry_directory_get_flat_contents(EntryDirectory* ed, DesktopEntrySet* desktop_entries, DesktopEntrySet* directory_entries, GSList** subdirs);
+
+
+typedef struct EntryDirectoryList EntryDirectoryList;
+
+EntryDirectoryList* entry_directory_list_new(void);
+EntryDirectoryList* entry_directory_list_ref(EntryDirectoryList* list);
+void entry_directory_list_unref(EntryDirectoryList* list);
+
+int entry_directory_list_get_length(EntryDirectoryList* list);
+gboolean _entry_directory_list_compare(const EntryDirectoryList* a, const EntryDirectoryList* b);
+
+void entry_directory_list_prepend(EntryDirectoryList* list, EntryDirectory* ed);
+void entry_directory_list_append_list(EntryDirectoryList* list, EntryDirectoryList* to_append);
+
+void entry_directory_list_add_monitors(EntryDirectoryList* list, EntryDirectoryChangedFunc callback, gpointer user_data);
+void entry_directory_list_remove_monitors(EntryDirectoryList* list, EntryDirectoryChangedFunc callback, gpointer user_data);
+
+DesktopEntry* entry_directory_list_get_directory (EntryDirectoryList* list, const char* relative_path);
+
+DesktopEntrySet* _entry_directory_list_get_all_desktops(EntryDirectoryList* list);
+void _entry_directory_list_empty_desktop_cache(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ENTRY_DIRECTORIES_H__ */
diff --git a/libmenu/libmate-menu-uninstalled.pc.in b/libmenu/libmate-menu-uninstalled.pc.in
new file mode 100644
index 0000000..e0b0496
--- /dev/null
+++ b/libmenu/libmate-menu-uninstalled.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libmate-menu
+Description: Desktop Menu Specification Implementation
+Requires: glib-2.0
+Version: @VERSION@
+Libs: ${pc_top_builddir}/${pcfiledir}/libmate-menu.la
+Cflags: -I${pc_top_builddir}/${pcfiledir}
diff --git a/libmenu/libmate-menu.pc.in b/libmenu/libmate-menu.pc.in
new file mode 100644
index 0000000..ab593d2
--- /dev/null
+++ b/libmenu/libmate-menu.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libmate-menu
+Description: Desktop Menu Specification Implementation
+Requires: glib-2.0
+Version: @VERSION@
+Libs: -L${libdir} -lmate-menu
+Cflags: -I${includedir}/mate-menus
diff --git a/libmenu/matemenu-tree.c b/libmenu/matemenu-tree.c
new file mode 100644
index 0000000..683eb55
--- /dev/null
+++ b/libmenu/matemenu-tree.c
@@ -0,0 +1,4520 @@
+/*
+ * Copyright (C) 2003, 2004 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include "matemenu-tree.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "menu-layout.h"
+#include "menu-monitor.h"
+#include "menu-util.h"
+#include "canonicalize.h"
+
+/*
+ * FIXME: it might be useful to be able to construct a menu
+ * tree from a traditional directory based menu hierarchy
+ * too.
+ */
+
+typedef enum
+{
+ MATEMENU_TREE_ABSOLUTE = 0,
+ MATEMENU_TREE_BASENAME = 1
+} MateMenuTreeType;
+
+struct MateMenuTree
+{
+ MateMenuTreeType type;
+ guint refcount;
+
+ char *basename;
+ char *absolute_path;
+ char *canonical_path;
+
+ MateMenuTreeFlags flags;
+ MateMenuTreeSortKey sort_key;
+
+ GSList *menu_file_monitors;
+
+ MenuLayoutNode *layout;
+ MateMenuTreeDirectory *root;
+
+ GSList *monitors;
+
+ gpointer user_data;
+ GDestroyNotify dnotify;
+
+ guint canonical : 1;
+};
+
+typedef struct
+{
+ MateMenuTreeChangedFunc callback;
+ gpointer user_data;
+} MateMenuTreeMonitor;
+
+struct MateMenuTreeItem
+{
+ MateMenuTreeItemType type;
+
+ MateMenuTreeDirectory *parent;
+
+ gpointer user_data;
+ GDestroyNotify dnotify;
+
+ guint refcount;
+};
+
+struct MateMenuTreeDirectory
+{
+ MateMenuTreeItem item;
+
+ DesktopEntry *directory_entry;
+ char *name;
+
+ GSList *entries;
+ GSList *subdirs;
+
+ MenuLayoutValues default_layout_values;
+ GSList *default_layout_info;
+ GSList *layout_info;
+ GSList *contents;
+
+ guint only_unallocated : 1;
+ guint is_root : 1;
+ guint is_nodisplay : 1;
+ guint layout_pending_separator : 1;
+ guint preprocessed : 1;
+
+ /* 16 bits should be more than enough; G_MAXUINT16 means no inline header */
+ guint will_inline_header : 16;
+};
+
+typedef struct
+{
+ MateMenuTreeDirectory directory;
+
+ MateMenuTree *tree;
+} MateMenuTreeDirectoryRoot;
+
+struct MateMenuTreeEntry
+{
+ MateMenuTreeItem item;
+
+ DesktopEntry *desktop_entry;
+ char *desktop_file_id;
+
+ guint is_excluded : 1;
+ guint is_nodisplay : 1;
+};
+
+struct MateMenuTreeSeparator
+{
+ MateMenuTreeItem item;
+};
+
+struct MateMenuTreeHeader
+{
+ MateMenuTreeItem item;
+
+ MateMenuTreeDirectory *directory;
+};
+
+struct MateMenuTreeAlias
+{
+ MateMenuTreeItem item;
+
+ MateMenuTreeDirectory *directory;
+ MateMenuTreeItem *aliased_item;
+};
+
+static MateMenuTree *matemenu_tree_new (MateMenuTreeType type,
+ const char *menu_file,
+ gboolean canonical,
+ MateMenuTreeFlags flags);
+static void matemenu_tree_load_layout (MateMenuTree *tree);
+static void matemenu_tree_force_reload (MateMenuTree *tree);
+static void matemenu_tree_build_from_layout (MateMenuTree *tree);
+static void matemenu_tree_force_rebuild (MateMenuTree *tree);
+static void matemenu_tree_resolve_files (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *layout);
+static void matemenu_tree_force_recanonicalize (MateMenuTree *tree);
+static void matemenu_tree_invoke_monitors (MateMenuTree *tree);
+
+static void matemenu_tree_item_unref_and_unset_parent (gpointer itemp);
+
+/*
+ * The idea is that we cache the menu tree for either a given
+ * menu basename or an absolute menu path.
+ * If no files exist in $XDG_DATA_DIRS for the basename or the
+ * absolute path doesn't exist we just return (and cache) the
+ * empty menu tree.
+ * We also add a file monitor for the basename in each dir in
+ * $XDG_DATA_DIRS, or the absolute path to the menu file, and
+ * re-compute if there are any changes.
+ */
+
+static GHashTable *matemenu_tree_cache = NULL;
+
+static inline char *
+get_cache_key (MateMenuTree *tree,
+ MateMenuTreeFlags flags)
+{
+ const char *tree_name;
+
+ switch (tree->type)
+ {
+ case MATEMENU_TREE_ABSOLUTE:
+ tree_name = tree->canonical ? tree->canonical_path : tree->absolute_path;
+ break;
+
+ case MATEMENU_TREE_BASENAME:
+ tree_name = tree->basename;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return g_strdup_printf ("%s:0x%x", tree_name, flags);
+}
+
+static void
+matemenu_tree_add_to_cache (MateMenuTree *tree,
+ MateMenuTreeFlags flags)
+{
+ char *cache_key;
+
+ if (matemenu_tree_cache == NULL)
+ {
+ matemenu_tree_cache =
+ g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ }
+
+ cache_key = get_cache_key (tree, flags);
+
+ menu_verbose ("Adding menu tree to cache: %s\n", cache_key);
+
+ g_hash_table_replace (matemenu_tree_cache, cache_key, tree);
+}
+
+static void
+matemenu_tree_remove_from_cache (MateMenuTree *tree,
+ MateMenuTreeFlags flags)
+{
+ char *cache_key;
+
+ cache_key = get_cache_key (tree, flags);
+
+ menu_verbose ("Removing menu tree from cache: %s\n", cache_key);
+
+ g_hash_table_remove (matemenu_tree_cache, cache_key);
+
+ g_free (cache_key);
+
+ if (g_hash_table_size (matemenu_tree_cache) == 0)
+ {
+ g_hash_table_destroy (matemenu_tree_cache);
+ matemenu_tree_cache = NULL;
+
+ _entry_directory_list_empty_desktop_cache ();
+ }
+}
+
+static MateMenuTree *
+matemenu_tree_lookup_from_cache (const char *tree_name,
+ MateMenuTreeFlags flags)
+{
+ MateMenuTree *retval;
+ char *cache_key;
+
+ if (matemenu_tree_cache == NULL)
+ return NULL;
+
+ cache_key = g_strdup_printf ("%s:0x%x", tree_name, flags);
+
+ menu_verbose ("Looking up '%s' from menu cache\n", cache_key);
+
+ retval = g_hash_table_lookup (matemenu_tree_cache, cache_key);
+
+ g_free (cache_key);
+
+ return retval ? matemenu_tree_ref (retval) : NULL;
+}
+
+typedef enum
+{
+ MENU_FILE_MONITOR_INVALID = 0,
+ MENU_FILE_MONITOR_FILE,
+ MENU_FILE_MONITOR_NONEXISTENT_FILE,
+ MENU_FILE_MONITOR_DIRECTORY
+} MenuFileMonitorType;
+
+typedef struct
+{
+ MenuFileMonitorType type;
+ MenuMonitor *monitor;
+} MenuFileMonitor;
+
+static void
+handle_nonexistent_menu_file_changed (MenuMonitor *monitor,
+ MenuMonitorEvent event,
+ const char *path,
+ MateMenuTree *tree)
+{
+ if (event == MENU_MONITOR_EVENT_CHANGED ||
+ event == MENU_MONITOR_EVENT_CREATED)
+ {
+ menu_verbose ("\"%s\" %s, marking tree for recanonicalization\n",
+ path,
+ event == MENU_MONITOR_EVENT_CREATED ? "created" : "changed");
+
+ matemenu_tree_force_recanonicalize (tree);
+ matemenu_tree_invoke_monitors (tree);
+ }
+}
+
+static void
+handle_menu_file_changed (MenuMonitor *monitor,
+ MenuMonitorEvent event,
+ const char *path,
+ MateMenuTree *tree)
+{
+ menu_verbose ("\"%s\" %s, marking tree for recanicalization\n",
+ path,
+ event == MENU_MONITOR_EVENT_CREATED ? "created" :
+ event == MENU_MONITOR_EVENT_CHANGED ? "changed" : "deleted");
+
+ matemenu_tree_force_recanonicalize (tree);
+ matemenu_tree_invoke_monitors (tree);
+}
+
+static void
+handle_menu_file_directory_changed (MenuMonitor *monitor,
+ MenuMonitorEvent event,
+ const char *path,
+ MateMenuTree *tree)
+{
+ if (!g_str_has_suffix (path, ".menu"))
+ return;
+
+ menu_verbose ("\"%s\" %s, marking tree for recanicalization\n",
+ path,
+ event == MENU_MONITOR_EVENT_CREATED ? "created" :
+ event == MENU_MONITOR_EVENT_CHANGED ? "changed" : "deleted");
+
+ matemenu_tree_force_recanonicalize (tree);
+ matemenu_tree_invoke_monitors (tree);
+}
+
+static void
+matemenu_tree_add_menu_file_monitor (MateMenuTree *tree,
+ const char *path,
+ MenuFileMonitorType type)
+{
+ MenuFileMonitor *monitor;
+
+ monitor = g_new0 (MenuFileMonitor, 1);
+
+ monitor->type = type;
+
+ switch (type)
+ {
+ case MENU_FILE_MONITOR_FILE:
+ menu_verbose ("Adding a menu file monitor for \"%s\"\n", path);
+
+ monitor->monitor = menu_get_file_monitor (path);
+ menu_monitor_add_notify (monitor->monitor,
+ (MenuMonitorNotifyFunc) handle_menu_file_changed,
+ tree);
+ break;
+
+ case MENU_FILE_MONITOR_NONEXISTENT_FILE:
+ menu_verbose ("Adding a menu file monitor for non-existent \"%s\"\n", path);
+
+ monitor->monitor = menu_get_file_monitor (path);
+ menu_monitor_add_notify (monitor->monitor,
+ (MenuMonitorNotifyFunc) handle_nonexistent_menu_file_changed,
+ tree);
+ break;
+
+ case MENU_FILE_MONITOR_DIRECTORY:
+ menu_verbose ("Adding a menu directory monitor for \"%s\"\n", path);
+
+ monitor->monitor = menu_get_directory_monitor (path);
+ menu_monitor_add_notify (monitor->monitor,
+ (MenuMonitorNotifyFunc) handle_menu_file_directory_changed,
+ tree);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ tree->menu_file_monitors = g_slist_prepend (tree->menu_file_monitors, monitor);
+}
+
+static void
+remove_menu_file_monitor (MenuFileMonitor *monitor,
+ MateMenuTree *tree)
+{
+ switch (monitor->type)
+ {
+ case MENU_FILE_MONITOR_FILE:
+ menu_monitor_remove_notify (monitor->monitor,
+ (MenuMonitorNotifyFunc) handle_menu_file_changed,
+ tree);
+ break;
+
+ case MENU_FILE_MONITOR_NONEXISTENT_FILE:
+ menu_monitor_remove_notify (monitor->monitor,
+ (MenuMonitorNotifyFunc) handle_nonexistent_menu_file_changed,
+ tree);
+ break;
+
+ case MENU_FILE_MONITOR_DIRECTORY:
+ menu_monitor_remove_notify (monitor->monitor,
+ (MenuMonitorNotifyFunc) handle_menu_file_directory_changed,
+ tree);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ menu_monitor_unref (monitor->monitor);
+ monitor->monitor = NULL;
+
+ monitor->type = MENU_FILE_MONITOR_INVALID;
+
+ g_free (monitor);
+}
+
+static void
+matemenu_tree_remove_menu_file_monitors (MateMenuTree *tree)
+{
+ menu_verbose ("Removing all menu file monitors\n");
+
+ g_slist_foreach (tree->menu_file_monitors,
+ (GFunc) remove_menu_file_monitor,
+ tree);
+ g_slist_free (tree->menu_file_monitors);
+ tree->menu_file_monitors = NULL;
+}
+
+static MateMenuTree *
+matemenu_tree_lookup_absolute (const char *absolute,
+ MateMenuTreeFlags flags)
+{
+ MateMenuTree *tree;
+ gboolean canonical;
+ const char *canonical_path;
+ char *freeme;
+
+ menu_verbose ("Looking up absolute path in tree cache: \"%s\"\n", absolute);
+
+ if ((tree = matemenu_tree_lookup_from_cache (absolute, flags)) != NULL)
+ return tree;
+
+ canonical = TRUE;
+ canonical_path = freeme = menu_canonicalize_file_name (absolute, FALSE);
+ if (canonical_path == NULL)
+ {
+ menu_verbose ("Failed to canonicalize absolute menu path \"%s\": %s\n",
+ absolute, g_strerror (errno));
+ canonical = FALSE;
+ canonical_path = absolute;
+ }
+
+ if ((tree = matemenu_tree_lookup_from_cache (canonical_path, flags)) != NULL)
+ return tree;
+
+ tree = matemenu_tree_new (MATEMENU_TREE_ABSOLUTE, canonical_path, canonical, flags);
+
+ g_free (freeme);
+
+ return tree;
+}
+
+static MateMenuTree *
+matemenu_tree_lookup_basename (const char *basename,
+ MateMenuTreeFlags flags)
+{
+ MateMenuTree *tree;
+
+ menu_verbose ("Looking up menu file in tree cache: \"%s\"\n", basename);
+
+ if ((tree = matemenu_tree_lookup_from_cache (basename, flags)) != NULL)
+ return tree;
+
+ return matemenu_tree_new (MATEMENU_TREE_BASENAME, basename, FALSE, flags);
+}
+
+static gboolean
+canonicalize_basename_with_config_dir (MateMenuTree *tree,
+ const char *basename,
+ const char *config_dir)
+{
+ char *path;
+
+ path = g_build_filename (config_dir, "menus", basename, NULL);
+
+ tree->canonical_path = menu_canonicalize_file_name (path, FALSE);
+ if (tree->canonical_path)
+ {
+ tree->canonical = TRUE;
+ matemenu_tree_add_menu_file_monitor (tree,
+ tree->canonical_path,
+ MENU_FILE_MONITOR_FILE);
+ }
+ else
+ {
+ matemenu_tree_add_menu_file_monitor (tree,
+ path,
+ MENU_FILE_MONITOR_NONEXISTENT_FILE);
+ }
+
+ g_free (path);
+
+ return tree->canonical;
+}
+
+static void
+canonicalize_basename (MateMenuTree *tree,
+ const char *basename)
+{
+ if (!canonicalize_basename_with_config_dir (tree,
+ basename,
+ g_get_user_config_dir ()))
+ {
+ const char * const *system_config_dirs;
+ int i;
+
+ system_config_dirs = g_get_system_config_dirs ();
+
+ i = 0;
+ while (system_config_dirs[i] != NULL)
+ {
+ if (canonicalize_basename_with_config_dir (tree,
+ basename,
+ system_config_dirs[i]))
+ break;
+
+ ++i;
+ }
+ }
+}
+
+static gboolean matemenu_tree_canonicalize_path(MateMenuTree* tree)
+{
+ if (tree->canonical)
+ return TRUE;
+
+ g_assert(tree->canonical_path == NULL);
+
+ if (tree->type == MATEMENU_TREE_BASENAME)
+ {
+ matemenu_tree_remove_menu_file_monitors (tree);
+
+ if (strcmp(tree->basename, "mate-applications.menu") == 0 && g_getenv("XDG_MENU_PREFIX"))
+ {
+ char* prefixed_basename;
+ prefixed_basename = g_strdup_printf("%s%s", g_getenv("XDG_MENU_PREFIX"), tree->basename);
+ canonicalize_basename(tree, prefixed_basename);
+ g_free(prefixed_basename);
+ }
+
+ if (!tree->canonical)
+ canonicalize_basename(tree, tree->basename);
+
+ if (tree->canonical)
+ menu_verbose("Successfully looked up menu_file for \"%s\": %s\n", tree->basename, tree->canonical_path);
+ else
+ menu_verbose("Failed to look up menu_file for \"%s\"\n", tree->basename);
+ }
+ else /* if (tree->type == MATEMENU_TREE_ABSOLUTE) */
+ {
+ tree->canonical_path = menu_canonicalize_file_name(tree->absolute_path, FALSE);
+
+ if (tree->canonical_path != NULL)
+ {
+ menu_verbose("Successfully looked up menu_file for \"%s\": %s\n", tree->absolute_path, tree->canonical_path);
+
+ /*
+ * Replace the cache entry with the canonicalized version
+ */
+ matemenu_tree_remove_from_cache (tree, tree->flags);
+
+ matemenu_tree_remove_menu_file_monitors(tree);
+ matemenu_tree_add_menu_file_monitor(tree, tree->canonical_path, MENU_FILE_MONITOR_FILE);
+
+ tree->canonical = TRUE;
+
+ matemenu_tree_add_to_cache (tree, tree->flags);
+ }
+ else
+ {
+ menu_verbose("Failed to look up menu_file for \"%s\"\n", tree->absolute_path);
+ }
+ }
+
+ return tree->canonical;
+}
+
+static void
+matemenu_tree_force_recanonicalize (MateMenuTree *tree)
+{
+ matemenu_tree_remove_menu_file_monitors (tree);
+
+ if (tree->canonical)
+ {
+ matemenu_tree_force_reload (tree);
+
+ g_free (tree->canonical_path);
+ tree->canonical_path = NULL;
+
+ tree->canonical = FALSE;
+ }
+}
+
+MateMenuTree* matemenu_tree_lookup(const char* menu_file, MateMenuTreeFlags flags)
+{
+ MateMenuTree *retval;
+
+ g_return_val_if_fail (menu_file != NULL, NULL);
+
+ flags &= MATEMENU_TREE_FLAGS_MASK;
+
+ if (g_path_is_absolute (menu_file))
+ retval = matemenu_tree_lookup_absolute (menu_file, flags);
+ else
+ retval = matemenu_tree_lookup_basename (menu_file, flags);
+
+ g_assert (retval != NULL);
+
+ return retval;
+}
+
+static MateMenuTree *
+matemenu_tree_new (MateMenuTreeType type,
+ const char *menu_file,
+ gboolean canonical,
+ MateMenuTreeFlags flags)
+{
+ MateMenuTree *tree;
+
+ tree = g_new0 (MateMenuTree, 1);
+
+ tree->type = type;
+ tree->flags = flags;
+ tree->refcount = 1;
+
+ tree->sort_key = MATEMENU_TREE_SORT_NAME;
+
+ if (tree->type == MATEMENU_TREE_BASENAME)
+ {
+ g_assert (canonical == FALSE);
+ tree->basename = g_strdup (menu_file);
+ }
+ else
+ {
+ tree->canonical = canonical != FALSE;
+ tree->absolute_path = g_strdup (menu_file);
+
+ if (tree->canonical)
+ {
+ tree->canonical_path = g_strdup (menu_file);
+ matemenu_tree_add_menu_file_monitor (tree,
+ tree->canonical_path,
+ MENU_FILE_MONITOR_FILE);
+ }
+ else
+ {
+ matemenu_tree_add_menu_file_monitor (tree,
+ tree->absolute_path,
+ MENU_FILE_MONITOR_NONEXISTENT_FILE);
+ }
+ }
+
+ matemenu_tree_add_to_cache (tree, tree->flags);
+
+ return tree;
+}
+
+MateMenuTree *
+matemenu_tree_ref (MateMenuTree *tree)
+{
+ g_return_val_if_fail (tree != NULL, NULL);
+ g_return_val_if_fail (tree->refcount > 0, NULL);
+
+ tree->refcount++;
+
+ return tree;
+}
+
+void
+matemenu_tree_unref (MateMenuTree *tree)
+{
+ g_return_if_fail (tree != NULL);
+ g_return_if_fail (tree->refcount >= 1);
+
+ if (--tree->refcount > 0)
+ return;
+
+ if (tree->dnotify)
+ tree->dnotify (tree->user_data);
+ tree->user_data = NULL;
+ tree->dnotify = NULL;
+
+ matemenu_tree_remove_from_cache (tree, tree->flags);
+
+ matemenu_tree_force_recanonicalize (tree);
+
+ if (tree->basename != NULL)
+ g_free (tree->basename);
+ tree->basename = NULL;
+
+ if (tree->absolute_path != NULL)
+ g_free (tree->absolute_path);
+ tree->absolute_path = NULL;
+
+ g_slist_foreach (tree->monitors, (GFunc) g_free, NULL);
+ g_slist_free (tree->monitors);
+ tree->monitors = NULL;
+
+ g_free (tree);
+}
+
+void
+matemenu_tree_set_user_data (MateMenuTree *tree,
+ gpointer user_data,
+ GDestroyNotify dnotify)
+{
+ g_return_if_fail (tree != NULL);
+
+ if (tree->dnotify != NULL)
+ tree->dnotify (tree->user_data);
+
+ tree->dnotify = dnotify;
+ tree->user_data = user_data;
+}
+
+gpointer
+matemenu_tree_get_user_data (MateMenuTree *tree)
+{
+ g_return_val_if_fail (tree != NULL, NULL);
+
+ return tree->user_data;
+}
+
+const char *
+matemenu_tree_get_menu_file (MateMenuTree *tree)
+{
+ /* FIXME: this is horribly ugly. But it's done to keep the API. Would be bad
+ * to break the API only for a "const char *" => "char *" change. The other
+ * alternative is to leak the memory, which is bad too. */
+ static char *ugly_result_cache = NULL;
+
+ g_return_val_if_fail (tree != NULL, NULL);
+
+ /* we need to canonicalize the path so we actually find out the real menu
+ * file that is being used -- and take into account XDG_MENU_PREFIX */
+ if (!matemenu_tree_canonicalize_path (tree))
+ return NULL;
+
+ if (ugly_result_cache != NULL)
+ {
+ g_free (ugly_result_cache);
+ ugly_result_cache = NULL;
+ }
+
+ if (tree->type == MATEMENU_TREE_BASENAME)
+ {
+ ugly_result_cache = g_path_get_basename (tree->canonical_path);
+ return ugly_result_cache;
+ }
+ else
+ return tree->absolute_path;
+}
+
+MateMenuTreeDirectory *
+matemenu_tree_get_root_directory (MateMenuTree *tree)
+{
+ g_return_val_if_fail (tree != NULL, NULL);
+
+ if (!tree->root)
+ {
+ matemenu_tree_build_from_layout (tree);
+
+ if (!tree->root)
+ return NULL;
+ }
+
+ return matemenu_tree_item_ref (tree->root);
+}
+
+static MateMenuTreeDirectory *
+find_path (MateMenuTreeDirectory *directory,
+ const char *path)
+{
+ const char *name;
+ char *slash;
+ char *freeme;
+ GSList *tmp;
+
+ while (path[0] == G_DIR_SEPARATOR) path++;
+
+ if (path[0] == '\0')
+ return directory;
+
+ freeme = NULL;
+ slash = strchr (path, G_DIR_SEPARATOR);
+ if (slash)
+ {
+ name = freeme = g_strndup (path, slash - path);
+ path = slash + 1;
+ }
+ else
+ {
+ name = path;
+ path = NULL;
+ }
+
+ tmp = directory->contents;
+ while (tmp != NULL)
+ {
+ MateMenuTreeItem *item = tmp->data;
+
+ if (matemenu_tree_item_get_type (item) != MATEMENU_TREE_ITEM_DIRECTORY)
+ {
+ tmp = tmp->next;
+ continue;
+ }
+
+ if (!strcmp (name, MATEMENU_TREE_DIRECTORY (item)->name))
+ {
+ g_free (freeme);
+
+ if (path)
+ return find_path (MATEMENU_TREE_DIRECTORY (item), path);
+ else
+ return MATEMENU_TREE_DIRECTORY (item);
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_free (freeme);
+
+ return NULL;
+}
+
+MateMenuTreeDirectory *
+matemenu_tree_get_directory_from_path (MateMenuTree *tree,
+ const char *path)
+{
+ MateMenuTreeDirectory *root;
+ MateMenuTreeDirectory *directory;
+
+ g_return_val_if_fail (tree != NULL, NULL);
+ g_return_val_if_fail (path != NULL, NULL);
+
+ if (path[0] != G_DIR_SEPARATOR)
+ return NULL;
+
+ if (!(root = matemenu_tree_get_root_directory (tree)))
+ return NULL;
+
+ directory = find_path (root, path);
+
+ matemenu_tree_item_unref (root);
+
+ return directory ? matemenu_tree_item_ref (directory) : NULL;
+}
+
+MateMenuTreeSortKey
+matemenu_tree_get_sort_key (MateMenuTree *tree)
+{
+ g_return_val_if_fail (tree != NULL, MATEMENU_TREE_SORT_NAME);
+ g_return_val_if_fail (tree->refcount > 0, MATEMENU_TREE_SORT_NAME);
+
+ return tree->sort_key;
+}
+
+void
+matemenu_tree_set_sort_key (MateMenuTree *tree,
+ MateMenuTreeSortKey sort_key)
+{
+ g_return_if_fail (tree != NULL);
+ g_return_if_fail (tree->refcount > 0);
+ g_return_if_fail (sort_key >= MATEMENU_TREE_SORT_FIRST);
+ g_return_if_fail (sort_key <= MATEMENU_TREE_SORT_LAST);
+
+ if (sort_key == tree->sort_key)
+ return;
+
+ tree->sort_key = sort_key;
+ matemenu_tree_force_rebuild (tree);
+}
+
+void
+matemenu_tree_add_monitor (MateMenuTree *tree,
+ MateMenuTreeChangedFunc callback,
+ gpointer user_data)
+{
+ MateMenuTreeMonitor *monitor;
+ GSList *tmp;
+
+ g_return_if_fail (tree != NULL);
+ g_return_if_fail (callback != NULL);
+
+ tmp = tree->monitors;
+ while (tmp != NULL)
+ {
+ monitor = tmp->data;
+
+ if (monitor->callback == callback &&
+ monitor->user_data == user_data)
+ break;
+
+ tmp = tmp->next;
+ }
+
+ if (tmp == NULL)
+ {
+ monitor = g_new0 (MateMenuTreeMonitor, 1);
+
+ monitor->callback = callback;
+ monitor->user_data = user_data;
+
+ tree->monitors = g_slist_append (tree->monitors, monitor);
+ }
+}
+
+void
+matemenu_tree_remove_monitor (MateMenuTree *tree,
+ MateMenuTreeChangedFunc callback,
+ gpointer user_data)
+{
+ GSList *tmp;
+
+ g_return_if_fail (tree != NULL);
+ g_return_if_fail (callback != NULL);
+
+ tmp = tree->monitors;
+ while (tmp != NULL)
+ {
+ MateMenuTreeMonitor *monitor = tmp->data;
+ GSList *next = tmp->next;
+
+ if (monitor->callback == callback &&
+ monitor->user_data == user_data)
+ {
+ tree->monitors = g_slist_delete_link (tree->monitors, tmp);
+ g_free (monitor);
+ }
+
+ tmp = next;
+ }
+}
+
+static void
+matemenu_tree_invoke_monitors (MateMenuTree *tree)
+{
+ GSList *tmp;
+
+ tmp = tree->monitors;
+ while (tmp != NULL)
+ {
+ MateMenuTreeMonitor *monitor = tmp->data;
+ GSList *next = tmp->next;
+
+ monitor->callback (tree, monitor->user_data);
+
+ tmp = next;
+ }
+}
+
+MateMenuTreeItemType
+matemenu_tree_item_get_type (MateMenuTreeItem *item)
+{
+ g_return_val_if_fail (item != NULL, 0);
+
+ return item->type;
+}
+
+MateMenuTreeDirectory *
+matemenu_tree_item_get_parent (MateMenuTreeItem *item)
+{
+ g_return_val_if_fail (item != NULL, NULL);
+
+ return item->parent ? matemenu_tree_item_ref (item->parent) : NULL;
+}
+
+static void
+matemenu_tree_item_set_parent (MateMenuTreeItem *item,
+ MateMenuTreeDirectory *parent)
+{
+ g_return_if_fail (item != NULL);
+
+ item->parent = parent;
+}
+
+GSList *
+matemenu_tree_directory_get_contents (MateMenuTreeDirectory *directory)
+{
+ GSList *retval;
+ GSList *tmp;
+
+ g_return_val_if_fail (directory != NULL, NULL);
+
+ retval = NULL;
+
+ tmp = directory->contents;
+ while (tmp != NULL)
+ {
+ retval = g_slist_prepend (retval,
+ matemenu_tree_item_ref (tmp->data));
+
+ tmp = tmp->next;
+ }
+
+ return g_slist_reverse (retval);
+}
+
+const char *
+matemenu_tree_directory_get_name (MateMenuTreeDirectory *directory)
+{
+ g_return_val_if_fail (directory != NULL, NULL);
+
+ if (!directory->directory_entry)
+ return directory->name;
+
+ return desktop_entry_get_name (directory->directory_entry);
+}
+
+const char *
+matemenu_tree_directory_get_comment (MateMenuTreeDirectory *directory)
+{
+ g_return_val_if_fail (directory != NULL, NULL);
+
+ if (!directory->directory_entry)
+ return NULL;
+
+ return desktop_entry_get_comment (directory->directory_entry);
+}
+
+const char* matemenu_tree_directory_get_icon(MateMenuTreeDirectory* directory)
+{
+ g_return_val_if_fail(directory != NULL, NULL);
+
+ if (!directory->directory_entry)
+ return NULL;
+
+ return desktop_entry_get_icon(directory->directory_entry);
+}
+
+const char *
+matemenu_tree_directory_get_desktop_file_path (MateMenuTreeDirectory *directory)
+{
+ g_return_val_if_fail (directory != NULL, NULL);
+
+ if (!directory->directory_entry)
+ return NULL;
+
+ return desktop_entry_get_path (directory->directory_entry);
+}
+
+const char *
+matemenu_tree_directory_get_menu_id (MateMenuTreeDirectory *directory)
+{
+ g_return_val_if_fail (directory != NULL, NULL);
+
+ return directory->name;
+}
+
+static void
+matemenu_tree_directory_set_tree (MateMenuTreeDirectory *directory,
+ MateMenuTree *tree)
+{
+ MateMenuTreeDirectoryRoot *root;
+
+ g_assert (directory != NULL);
+ g_assert (directory->is_root);
+
+ root = (MateMenuTreeDirectoryRoot *) directory;
+
+ root->tree = tree;
+}
+
+MateMenuTree *
+matemenu_tree_directory_get_tree (MateMenuTreeDirectory *directory)
+{
+ MateMenuTreeDirectoryRoot *root;
+
+ g_return_val_if_fail (directory != NULL, NULL);
+
+ while (MATEMENU_TREE_ITEM (directory)->parent != NULL)
+ directory = MATEMENU_TREE_DIRECTORY (MATEMENU_TREE_ITEM (directory)->parent);
+
+ if (!directory->is_root)
+ return NULL;
+
+ root = (MateMenuTreeDirectoryRoot *) directory;
+
+ if (root->tree)
+ matemenu_tree_ref (root->tree);
+
+ return root->tree;
+}
+
+gboolean
+matemenu_tree_directory_get_is_nodisplay (MateMenuTreeDirectory *directory)
+{
+ g_return_val_if_fail (directory != NULL, FALSE);
+
+ return directory->is_nodisplay;
+}
+
+static void
+append_directory_path (MateMenuTreeDirectory *directory,
+ GString *path)
+{
+
+ if (!directory->item.parent)
+ {
+ g_string_append_c (path, G_DIR_SEPARATOR);
+ return;
+ }
+
+ append_directory_path (directory->item.parent, path);
+
+ g_string_append (path, directory->name);
+ g_string_append_c (path, G_DIR_SEPARATOR);
+}
+
+char *
+matemenu_tree_directory_make_path (MateMenuTreeDirectory *directory,
+ MateMenuTreeEntry *entry)
+{
+ GString *path;
+
+ g_return_val_if_fail (directory != NULL, NULL);
+
+ path = g_string_new (NULL);
+
+ append_directory_path (directory, path);
+
+ if (entry != NULL)
+ g_string_append (path,
+ desktop_entry_get_basename (entry->desktop_entry));
+
+ return g_string_free (path, FALSE);
+}
+
+const char *
+matemenu_tree_entry_get_name (MateMenuTreeEntry *entry)
+{
+ g_return_val_if_fail (entry != NULL, NULL);
+
+ return desktop_entry_get_name (entry->desktop_entry);
+}
+
+const char *
+matemenu_tree_entry_get_generic_name (MateMenuTreeEntry *entry)
+{
+ g_return_val_if_fail (entry != NULL, NULL);
+
+ return desktop_entry_get_generic_name (entry->desktop_entry);
+}
+
+const char *
+matemenu_tree_entry_get_display_name (MateMenuTreeEntry *entry)
+{
+ const char *display_name;
+
+ g_return_val_if_fail (entry != NULL, NULL);
+
+ display_name = desktop_entry_get_full_name (entry->desktop_entry);
+ if (!display_name || display_name[0] == '\0')
+ display_name = desktop_entry_get_name (entry->desktop_entry);
+
+ return display_name;
+}
+
+const char *
+matemenu_tree_entry_get_comment (MateMenuTreeEntry *entry)
+{
+ g_return_val_if_fail (entry != NULL, NULL);
+
+ return desktop_entry_get_comment (entry->desktop_entry);
+}
+
+const char* matemenu_tree_entry_get_icon(MateMenuTreeEntry *entry)
+{
+ g_return_val_if_fail (entry != NULL, NULL);
+
+ return desktop_entry_get_icon(entry->desktop_entry);
+}
+
+const char* matemenu_tree_entry_get_exec(MateMenuTreeEntry* entry)
+{
+ g_return_val_if_fail(entry != NULL, NULL);
+
+ return desktop_entry_get_exec(entry->desktop_entry);
+}
+
+gboolean matemenu_tree_entry_get_launch_in_terminal(MateMenuTreeEntry* entry)
+{
+ g_return_val_if_fail(entry != NULL, FALSE);
+
+ return desktop_entry_get_launch_in_terminal(entry->desktop_entry);
+}
+
+const char* matemenu_tree_entry_get_desktop_file_path(MateMenuTreeEntry* entry)
+{
+ g_return_val_if_fail(entry != NULL, NULL);
+
+ return desktop_entry_get_path(entry->desktop_entry);
+}
+
+const char* matemenu_tree_entry_get_desktop_file_id(MateMenuTreeEntry* entry)
+{
+ g_return_val_if_fail(entry != NULL, NULL);
+
+ return entry->desktop_file_id;
+}
+
+gboolean matemenu_tree_entry_get_is_excluded(MateMenuTreeEntry* entry)
+{
+ g_return_val_if_fail(entry != NULL, FALSE);
+
+ return entry->is_excluded;
+}
+
+gboolean matemenu_tree_entry_get_is_nodisplay(MateMenuTreeEntry* entry)
+{
+ g_return_val_if_fail(entry != NULL, FALSE);
+
+ return entry->is_nodisplay;
+}
+
+MateMenuTreeDirectory* matemenu_tree_header_get_directory(MateMenuTreeHeader* header)
+{
+ g_return_val_if_fail (header != NULL, NULL);
+
+ return matemenu_tree_item_ref(header->directory);
+}
+
+MateMenuTreeDirectory* matemenu_tree_alias_get_directory(MateMenuTreeAlias* alias)
+{
+ g_return_val_if_fail (alias != NULL, NULL);
+
+ return matemenu_tree_item_ref(alias->directory);
+}
+
+MateMenuTreeItem *
+matemenu_tree_alias_get_item (MateMenuTreeAlias *alias)
+{
+ g_return_val_if_fail (alias != NULL, NULL);
+
+ return matemenu_tree_item_ref (alias->aliased_item);
+}
+
+static MateMenuTreeDirectory *
+matemenu_tree_directory_new (MateMenuTreeDirectory *parent,
+ const char *name,
+ gboolean is_root)
+{
+ MateMenuTreeDirectory *retval;
+
+ if (!is_root)
+ {
+ retval = g_new0 (MateMenuTreeDirectory, 1);
+ }
+ else
+ {
+ MateMenuTreeDirectoryRoot *root;
+
+ root = g_new0 (MateMenuTreeDirectoryRoot, 1);
+
+ retval = MATEMENU_TREE_DIRECTORY (root);
+
+ retval->is_root = TRUE;
+ }
+
+
+ retval->item.type = MATEMENU_TREE_ITEM_DIRECTORY;
+ retval->item.parent = parent;
+ retval->item.refcount = 1;
+
+ retval->name = g_strdup (name);
+ retval->directory_entry = NULL;
+ retval->entries = NULL;
+ retval->subdirs = NULL;
+ retval->default_layout_info = NULL;
+ retval->layout_info = NULL;
+ retval->contents = NULL;
+ retval->only_unallocated = FALSE;
+ retval->is_nodisplay = FALSE;
+ retval->layout_pending_separator = FALSE;
+ retval->preprocessed = FALSE;
+ retval->will_inline_header = G_MAXUINT16;
+
+ retval->default_layout_values.mask = MENU_LAYOUT_VALUES_NONE;
+ retval->default_layout_values.show_empty = FALSE;
+ retval->default_layout_values.inline_menus = FALSE;
+ retval->default_layout_values.inline_limit = 4;
+ retval->default_layout_values.inline_header = FALSE;
+ retval->default_layout_values.inline_alias = FALSE;
+
+ return retval;
+}
+
+static void
+matemenu_tree_directory_finalize (MateMenuTreeDirectory *directory)
+{
+ g_assert (directory->item.refcount == 0);
+
+ g_slist_foreach (directory->contents,
+ (GFunc) matemenu_tree_item_unref_and_unset_parent,
+ NULL);
+ g_slist_free (directory->contents);
+ directory->contents = NULL;
+
+ g_slist_foreach (directory->default_layout_info,
+ (GFunc) menu_layout_node_unref,
+ NULL);
+ g_slist_free (directory->default_layout_info);
+ directory->default_layout_info = NULL;
+
+ g_slist_foreach (directory->layout_info,
+ (GFunc) menu_layout_node_unref,
+ NULL);
+ g_slist_free (directory->layout_info);
+ directory->layout_info = NULL;
+
+ g_slist_foreach (directory->subdirs,
+ (GFunc) matemenu_tree_item_unref_and_unset_parent,
+ NULL);
+ g_slist_free (directory->subdirs);
+ directory->subdirs = NULL;
+
+ g_slist_foreach (directory->entries,
+ (GFunc) matemenu_tree_item_unref_and_unset_parent,
+ NULL);
+ g_slist_free (directory->entries);
+ directory->entries = NULL;
+
+ if (directory->directory_entry)
+ desktop_entry_unref (directory->directory_entry);
+ directory->directory_entry = NULL;
+
+ g_free (directory->name);
+ directory->name = NULL;
+}
+
+static MateMenuTreeSeparator *
+matemenu_tree_separator_new (MateMenuTreeDirectory *parent)
+{
+ MateMenuTreeSeparator *retval;
+
+ retval = g_new0 (MateMenuTreeSeparator, 1);
+
+ retval->item.type = MATEMENU_TREE_ITEM_SEPARATOR;
+ retval->item.parent = parent;
+ retval->item.refcount = 1;
+
+ return retval;
+}
+
+static MateMenuTreeHeader *
+matemenu_tree_header_new (MateMenuTreeDirectory *parent,
+ MateMenuTreeDirectory *directory)
+{
+ MateMenuTreeHeader *retval;
+
+ retval = g_new0 (MateMenuTreeHeader, 1);
+
+ retval->item.type = MATEMENU_TREE_ITEM_HEADER;
+ retval->item.parent = parent;
+ retval->item.refcount = 1;
+
+ retval->directory = matemenu_tree_item_ref (directory);
+
+ matemenu_tree_item_set_parent (MATEMENU_TREE_ITEM (retval->directory), NULL);
+
+ return retval;
+}
+
+static void
+matemenu_tree_header_finalize (MateMenuTreeHeader *header)
+{
+ g_assert (header->item.refcount == 0);
+
+ if (header->directory != NULL)
+ matemenu_tree_item_unref (header->directory);
+ header->directory = NULL;
+}
+
+static MateMenuTreeAlias *
+matemenu_tree_alias_new (MateMenuTreeDirectory *parent,
+ MateMenuTreeDirectory *directory,
+ MateMenuTreeItem *item)
+{
+ MateMenuTreeAlias *retval;
+
+ retval = g_new0 (MateMenuTreeAlias, 1);
+
+ retval->item.type = MATEMENU_TREE_ITEM_ALIAS;
+ retval->item.parent = parent;
+ retval->item.refcount = 1;
+
+ retval->directory = matemenu_tree_item_ref (directory);
+ if (item->type != MATEMENU_TREE_ITEM_ALIAS)
+ retval->aliased_item = matemenu_tree_item_ref (item);
+ else
+ retval->aliased_item = matemenu_tree_item_ref (matemenu_tree_alias_get_item (MATEMENU_TREE_ALIAS (item)));
+
+ matemenu_tree_item_set_parent (MATEMENU_TREE_ITEM (retval->directory), NULL);
+ matemenu_tree_item_set_parent (retval->aliased_item, NULL);
+
+ return retval;
+}
+
+static void
+matemenu_tree_alias_finalize (MateMenuTreeAlias *alias)
+{
+ g_assert (alias->item.refcount == 0);
+
+ if (alias->directory != NULL)
+ matemenu_tree_item_unref (alias->directory);
+ alias->directory = NULL;
+
+ if (alias->aliased_item != NULL)
+ matemenu_tree_item_unref (alias->aliased_item);
+ alias->aliased_item = NULL;
+}
+
+static MateMenuTreeEntry *
+matemenu_tree_entry_new (MateMenuTreeDirectory *parent,
+ DesktopEntry *desktop_entry,
+ const char *desktop_file_id,
+ gboolean is_excluded,
+ gboolean is_nodisplay)
+{
+ MateMenuTreeEntry *retval;
+
+ retval = g_new0 (MateMenuTreeEntry, 1);
+
+ retval->item.type = MATEMENU_TREE_ITEM_ENTRY;
+ retval->item.parent = parent;
+ retval->item.refcount = 1;
+
+ retval->desktop_entry = desktop_entry_ref (desktop_entry);
+ retval->desktop_file_id = g_strdup (desktop_file_id);
+ retval->is_excluded = is_excluded != FALSE;
+ retval->is_nodisplay = is_nodisplay != FALSE;
+
+ return retval;
+}
+
+static void
+matemenu_tree_entry_finalize (MateMenuTreeEntry *entry)
+{
+ g_assert (entry->item.refcount == 0);
+
+ g_free (entry->desktop_file_id);
+ entry->desktop_file_id = NULL;
+
+ if (entry->desktop_entry)
+ desktop_entry_unref (entry->desktop_entry);
+ entry->desktop_entry = NULL;
+}
+
+static int
+matemenu_tree_entry_compare_by_id (MateMenuTreeItem *a,
+ MateMenuTreeItem *b)
+{
+ if (a->type == MATEMENU_TREE_ITEM_ALIAS)
+ a = MATEMENU_TREE_ALIAS (a)->aliased_item;
+
+ if (b->type == MATEMENU_TREE_ITEM_ALIAS)
+ b = MATEMENU_TREE_ALIAS (b)->aliased_item;
+
+ return strcmp (MATEMENU_TREE_ENTRY (a)->desktop_file_id,
+ MATEMENU_TREE_ENTRY (b)->desktop_file_id);
+}
+
+gpointer matemenu_tree_item_ref(gpointer itemp)
+{
+ MateMenuTreeItem* item = (MateMenuTreeItem*) itemp;
+
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(item->refcount > 0, NULL);
+
+ item->refcount++;
+
+ return item;
+}
+
+void
+matemenu_tree_item_unref (gpointer itemp)
+{
+ MateMenuTreeItem *item;
+
+ item = (MateMenuTreeItem *) itemp;
+
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (item->refcount > 0);
+
+ if (--item->refcount == 0)
+ {
+ switch (item->type)
+ {
+ case MATEMENU_TREE_ITEM_DIRECTORY:
+ matemenu_tree_directory_finalize (MATEMENU_TREE_DIRECTORY (item));
+ break;
+
+ case MATEMENU_TREE_ITEM_ENTRY:
+ matemenu_tree_entry_finalize (MATEMENU_TREE_ENTRY (item));
+ break;
+
+ case MATEMENU_TREE_ITEM_SEPARATOR:
+ break;
+
+ case MATEMENU_TREE_ITEM_HEADER:
+ matemenu_tree_header_finalize (MATEMENU_TREE_HEADER (item));
+ break;
+
+ case MATEMENU_TREE_ITEM_ALIAS:
+ matemenu_tree_alias_finalize (MATEMENU_TREE_ALIAS (item));
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ if (item->dnotify)
+ item->dnotify (item->user_data);
+ item->user_data = NULL;
+ item->dnotify = NULL;
+
+ item->parent = NULL;
+
+ g_free (item);
+ }
+}
+
+static void
+matemenu_tree_item_unref_and_unset_parent (gpointer itemp)
+{
+ MateMenuTreeItem *item;
+
+ item = (MateMenuTreeItem *) itemp;
+
+ g_return_if_fail (item != NULL);
+
+ matemenu_tree_item_set_parent (item, NULL);
+ matemenu_tree_item_unref (item);
+}
+
+void
+matemenu_tree_item_set_user_data (MateMenuTreeItem *item,
+ gpointer user_data,
+ GDestroyNotify dnotify)
+{
+ g_return_if_fail (item != NULL);
+
+ if (item->dnotify != NULL)
+ item->dnotify (item->user_data);
+
+ item->dnotify = dnotify;
+ item->user_data = user_data;
+}
+
+gpointer
+matemenu_tree_item_get_user_data (MateMenuTreeItem *item)
+{
+ g_return_val_if_fail (item != NULL, NULL);
+
+ return item->user_data;
+}
+
+static inline const char *
+matemenu_tree_item_compare_get_name_helper (MateMenuTreeItem *item,
+ MateMenuTreeSortKey sort_key)
+{
+ const char *name;
+
+ name = NULL;
+
+ switch (item->type)
+ {
+ case MATEMENU_TREE_ITEM_DIRECTORY:
+ if (MATEMENU_TREE_DIRECTORY (item)->directory_entry)
+ name = desktop_entry_get_name (MATEMENU_TREE_DIRECTORY (item)->directory_entry);
+ else
+ name = MATEMENU_TREE_DIRECTORY (item)->name;
+ break;
+
+ case MATEMENU_TREE_ITEM_ENTRY:
+ switch (sort_key)
+ {
+ case MATEMENU_TREE_SORT_NAME:
+ name = desktop_entry_get_name (MATEMENU_TREE_ENTRY (item)->desktop_entry);
+ break;
+ case MATEMENU_TREE_SORT_DISPLAY_NAME:
+ name = matemenu_tree_entry_get_display_name (MATEMENU_TREE_ENTRY (item));
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ break;
+
+ case MATEMENU_TREE_ITEM_ALIAS:
+ {
+ MateMenuTreeItem *dir;
+ dir = MATEMENU_TREE_ITEM (MATEMENU_TREE_ALIAS (item)->directory);
+ name = matemenu_tree_item_compare_get_name_helper (dir, sort_key);
+ }
+ break;
+
+ case MATEMENU_TREE_ITEM_SEPARATOR:
+ case MATEMENU_TREE_ITEM_HEADER:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return name;
+}
+
+static int
+matemenu_tree_item_compare (MateMenuTreeItem *a,
+ MateMenuTreeItem *b,
+ gpointer sort_key_p)
+{
+ const char *name_a;
+ const char *name_b;
+ MateMenuTreeSortKey sort_key;
+
+ sort_key = GPOINTER_TO_INT (sort_key_p);
+
+ name_a = matemenu_tree_item_compare_get_name_helper (a, sort_key);
+ name_b = matemenu_tree_item_compare_get_name_helper (b, sort_key);
+
+ return g_utf8_collate (name_a, name_b);
+}
+
+static MenuLayoutNode *
+find_menu_child (MenuLayoutNode *layout)
+{
+ MenuLayoutNode *child;
+
+ child = menu_layout_node_get_children (layout);
+ while (child && menu_layout_node_get_type (child) != MENU_LAYOUT_NODE_MENU)
+ child = menu_layout_node_get_next (child);
+
+ return child;
+}
+
+static void
+merge_resolved_children (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *where,
+ MenuLayoutNode *from)
+{
+ MenuLayoutNode *insert_after;
+ MenuLayoutNode *menu_child;
+ MenuLayoutNode *from_child;
+
+ matemenu_tree_resolve_files (tree, loaded_menu_files, from);
+
+ insert_after = where;
+ g_assert (menu_layout_node_get_type (insert_after) != MENU_LAYOUT_NODE_ROOT);
+ g_assert (menu_layout_node_get_parent (insert_after) != NULL);
+
+ /* skip root node */
+ menu_child = find_menu_child (from);
+ g_assert (menu_child != NULL);
+ g_assert (menu_layout_node_get_type (menu_child) == MENU_LAYOUT_NODE_MENU);
+
+ /* merge children of toplevel <Menu> */
+ from_child = menu_layout_node_get_children (menu_child);
+ while (from_child != NULL)
+ {
+ MenuLayoutNode *next;
+
+ next = menu_layout_node_get_next (from_child);
+
+ menu_verbose ("Merging ");
+ menu_debug_print_layout (from_child, FALSE);
+ menu_verbose (" after ");
+ menu_debug_print_layout (insert_after, FALSE);
+
+ switch (menu_layout_node_get_type (from_child))
+ {
+ case MENU_LAYOUT_NODE_NAME:
+ menu_layout_node_unlink (from_child); /* delete this */
+ break;
+
+ default:
+ menu_layout_node_steal (from_child);
+ menu_layout_node_insert_after (insert_after, from_child);
+ menu_layout_node_unref (from_child);
+
+ insert_after = from_child;
+ break;
+ }
+
+ from_child = next;
+ }
+}
+
+static gboolean
+load_merge_file (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ const char *filename,
+ gboolean is_canonical,
+ gboolean add_monitor,
+ MenuLayoutNode *where)
+{
+ MenuLayoutNode *to_merge;
+ const char *canonical;
+ char *freeme;
+ gboolean retval;
+
+ freeme = NULL;
+ retval = FALSE;
+
+ if (!is_canonical)
+ {
+ canonical = freeme = menu_canonicalize_file_name (filename, FALSE);
+ if (canonical == NULL)
+ {
+ if (add_monitor)
+ matemenu_tree_add_menu_file_monitor (tree,
+ filename,
+ MENU_FILE_MONITOR_NONEXISTENT_FILE);
+
+ menu_verbose ("Failed to canonicalize merge file path \"%s\": %s\n",
+ filename, g_strerror (errno));
+ goto out;
+ }
+ }
+ else
+ {
+ canonical = filename;
+ }
+
+ if (g_hash_table_lookup (loaded_menu_files, canonical) != NULL)
+ {
+ g_warning ("Not loading \"%s\": recursive loop detected in .menu files",
+ canonical);
+ retval = TRUE;
+ goto out;
+ }
+
+ menu_verbose ("Merging file \"%s\"\n", canonical);
+
+ to_merge = menu_layout_load (canonical, NULL, NULL);
+ if (to_merge == NULL)
+ {
+ menu_verbose ("No menu for file \"%s\" found when merging\n",
+ canonical);
+ goto out;
+ }
+
+ retval = TRUE;
+
+ g_hash_table_insert (loaded_menu_files, (char *) canonical, GUINT_TO_POINTER (TRUE));
+
+ if (add_monitor)
+ matemenu_tree_add_menu_file_monitor (tree,
+ canonical,
+ MENU_FILE_MONITOR_FILE);
+
+ merge_resolved_children (tree, loaded_menu_files, where, to_merge);
+
+ g_hash_table_remove (loaded_menu_files, canonical);
+
+ menu_layout_node_unref (to_merge);
+
+ out:
+ if (freeme)
+ g_free (freeme);
+
+ return retval;
+}
+
+static gboolean
+load_merge_file_with_config_dir (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ const char *menu_file,
+ const char *config_dir,
+ MenuLayoutNode *where)
+{
+ char *merge_file;
+ gboolean loaded;
+
+ loaded = FALSE;
+
+ merge_file = g_build_filename (config_dir, "menus", menu_file, NULL);
+
+ if (load_merge_file (tree, loaded_menu_files, merge_file, FALSE, TRUE, where))
+ loaded = TRUE;
+
+ g_free (merge_file);
+
+ return loaded;
+}
+
+static gboolean
+compare_basedir_to_config_dir (const char *canonical_basedir,
+ const char *config_dir)
+{
+ char *dirname;
+ char *canonical_menus_dir;
+ gboolean retval;
+
+ menu_verbose ("Checking to see if basedir '%s' is in '%s'\n",
+ canonical_basedir, config_dir);
+
+ dirname = g_build_filename (config_dir, "menus", NULL);
+
+ retval = FALSE;
+
+ canonical_menus_dir = menu_canonicalize_file_name (dirname, FALSE);
+ if (canonical_menus_dir != NULL &&
+ strcmp (canonical_basedir, canonical_menus_dir) == 0)
+ {
+ retval = TRUE;
+ }
+
+ g_free (canonical_menus_dir);
+ g_free (dirname);
+
+ return retval;
+}
+
+static gboolean
+load_parent_merge_file_from_basename (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *layout,
+ const char *menu_file,
+ const char *canonical_basedir)
+{
+ gboolean found_basedir;
+ const char * const *system_config_dirs;
+ int i;
+
+ /* We're not interested in menu files that are in directories which are not a
+ * parent of the base directory of this menu file */
+ found_basedir = compare_basedir_to_config_dir (canonical_basedir,
+ g_get_user_config_dir ());
+
+ system_config_dirs = g_get_system_config_dirs ();
+
+ i = 0;
+ while (system_config_dirs[i] != NULL)
+ {
+ if (!found_basedir)
+ {
+ found_basedir = compare_basedir_to_config_dir (canonical_basedir,
+ system_config_dirs[i]);
+ }
+ else
+ {
+ menu_verbose ("Looking for parent menu file '%s' in '%s'\n",
+ menu_file, system_config_dirs[i]);
+
+ if (load_merge_file_with_config_dir (tree,
+ loaded_menu_files,
+ menu_file,
+ system_config_dirs[i],
+ layout))
+ {
+ break;
+ }
+ }
+
+ ++i;
+ }
+
+ return system_config_dirs[i] != NULL;
+}
+
+static gboolean load_parent_merge_file(MateMenuTree* tree, GHashTable* loaded_menu_files, MenuLayoutNode* layout)
+{
+ MenuLayoutNode* root;
+ const char* basedir;
+ const char* menu_name;
+ char* canonical_basedir;
+ char* menu_file;
+ gboolean found;
+
+ root = menu_layout_node_get_root(layout);
+
+ basedir = menu_layout_node_root_get_basedir(root);
+ menu_name = menu_layout_node_root_get_name(root);
+
+ canonical_basedir = menu_canonicalize_file_name(basedir, FALSE);
+
+ if (canonical_basedir == NULL)
+ {
+ menu_verbose("Menu basedir '%s' no longer exists, not merging parent\n", basedir);
+ return FALSE;
+ }
+
+ found = FALSE;
+ menu_file = g_strconcat(menu_name, ".menu", NULL);
+
+ if (strcmp(menu_file, "mate-applications.menu") == 0 && g_getenv("XDG_MENU_PREFIX"))
+ {
+ char* prefixed_basename;
+ prefixed_basename = g_strdup_printf("%s%s", g_getenv("XDG_MENU_PREFIX"), menu_file);
+ found = load_parent_merge_file_from_basename(tree, loaded_menu_files, layout, prefixed_basename, canonical_basedir);
+ g_free(prefixed_basename);
+ }
+
+ if (!found)
+ {
+ found = load_parent_merge_file_from_basename(tree, loaded_menu_files, layout, menu_file, canonical_basedir);
+ }
+
+ g_free(menu_file);
+ g_free(canonical_basedir);
+
+ return found;
+}
+
+static void
+load_merge_dir (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ const char *dirname,
+ MenuLayoutNode *where)
+{
+ GDir *dir;
+ const char *menu_file;
+
+ menu_verbose ("Loading merge dir \"%s\"\n", dirname);
+
+ matemenu_tree_add_menu_file_monitor (tree,
+ dirname,
+ MENU_FILE_MONITOR_DIRECTORY);
+
+ if ((dir = g_dir_open (dirname, 0, NULL)) == NULL)
+ return;
+
+ while ((menu_file = g_dir_read_name (dir)))
+ {
+ if (g_str_has_suffix (menu_file, ".menu"))
+ {
+ char *full_path;
+
+ full_path = g_build_filename (dirname, menu_file, NULL);
+
+ load_merge_file (tree, loaded_menu_files, full_path, TRUE, FALSE, where);
+
+ g_free (full_path);
+ }
+ }
+
+ g_dir_close (dir);
+}
+
+static void
+load_merge_dir_with_config_dir (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ const char *config_dir,
+ const char *dirname,
+ MenuLayoutNode *where)
+{
+ char *path;
+
+ path = g_build_filename (config_dir, "menus", dirname, NULL);
+
+ load_merge_dir (tree, loaded_menu_files, path, where);
+
+ g_free (path);
+}
+
+static void
+resolve_merge_file (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *layout)
+{
+ char *filename;
+
+ if (menu_layout_node_merge_file_get_type (layout) == MENU_MERGE_FILE_TYPE_PARENT)
+ {
+ if (load_parent_merge_file (tree, loaded_menu_files, layout))
+ return;
+ }
+
+ filename = menu_layout_node_get_content_as_path (layout);
+ if (filename == NULL)
+ {
+ menu_verbose ("didn't get node content as a path, not merging file\n");
+ }
+ else
+ {
+ load_merge_file (tree, loaded_menu_files, filename, FALSE, TRUE, layout);
+
+ g_free (filename);
+ }
+
+ /* remove the now-replaced node */
+ menu_layout_node_unlink (layout);
+}
+
+static void
+resolve_merge_dir (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *layout)
+{
+ char *path;
+
+ path = menu_layout_node_get_content_as_path (layout);
+ if (path == NULL)
+ {
+ menu_verbose ("didn't get layout node content as a path, not merging dir\n");
+ }
+ else
+ {
+ load_merge_dir (tree, loaded_menu_files, path, layout);
+
+ g_free (path);
+ }
+
+ /* remove the now-replaced node */
+ menu_layout_node_unlink (layout);
+}
+
+static MenuLayoutNode *
+add_app_dir (MateMenuTree *tree,
+ MenuLayoutNode *before,
+ const char *data_dir)
+{
+ MenuLayoutNode *tmp;
+ char *dirname;
+
+ tmp = menu_layout_node_new (MENU_LAYOUT_NODE_APP_DIR);
+ dirname = g_build_filename (data_dir, "applications", NULL);
+ menu_layout_node_set_content (tmp, dirname);
+ menu_layout_node_insert_before (before, tmp);
+ menu_layout_node_unref (before);
+
+ menu_verbose ("Adding <AppDir>%s</AppDir> in <DefaultAppDirs/>\n",
+ dirname);
+
+ g_free (dirname);
+
+ return tmp;
+}
+
+static void
+resolve_default_app_dirs (MateMenuTree *tree,
+ MenuLayoutNode *layout)
+{
+ MenuLayoutNode *before;
+ const char * const *system_data_dirs;
+ int i;
+
+ system_data_dirs = g_get_system_data_dirs ();
+
+ before = add_app_dir (tree,
+ menu_layout_node_ref (layout),
+ g_get_user_data_dir ());
+
+ i = 0;
+ while (system_data_dirs[i] != NULL)
+ {
+ before = add_app_dir (tree, before, system_data_dirs[i]);
+
+ ++i;
+ }
+
+ menu_layout_node_unref (before);
+
+ /* remove the now-replaced node */
+ menu_layout_node_unlink (layout);
+}
+
+static MenuLayoutNode* add_directory_dir(MateMenuTree* tree, MenuLayoutNode* before, const char* data_dir)
+{
+ MenuLayoutNode* tmp;
+ char* dirname;
+
+ tmp = menu_layout_node_new(MENU_LAYOUT_NODE_DIRECTORY_DIR);
+ dirname = g_build_filename(data_dir, "desktop-directories", NULL);
+ menu_layout_node_set_content(tmp, dirname);
+ menu_layout_node_insert_before(before, tmp);
+ menu_layout_node_unref(before);
+
+ menu_verbose("Adding <DirectoryDir>%s</DirectoryDir> in <DefaultDirectoryDirs/>\n", dirname);
+
+ g_free(dirname);
+
+ return tmp;
+}
+
+static void
+resolve_default_directory_dirs (MateMenuTree *tree,
+ MenuLayoutNode *layout)
+{
+ MenuLayoutNode *before;
+ const char * const *system_data_dirs;
+ int i;
+
+ system_data_dirs = g_get_system_data_dirs ();
+
+ before = add_directory_dir (tree,
+ menu_layout_node_ref (layout),
+ g_get_user_data_dir ());
+
+ i = 0;
+ while (system_data_dirs[i] != NULL)
+ {
+ before = add_directory_dir (tree, before, system_data_dirs[i]);
+
+ ++i;
+ }
+
+ menu_layout_node_unref (before);
+
+ /* remove the now-replaced node */
+ menu_layout_node_unlink (layout);
+}
+
+static void
+resolve_default_merge_dirs (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *layout)
+{
+ MenuLayoutNode *root;
+ const char *menu_name;
+ char *merge_name;
+ const char * const *system_config_dirs;
+ int i;
+
+ root = menu_layout_node_get_root (layout);
+ menu_name = menu_layout_node_root_get_name (root);
+
+ merge_name = g_strconcat (menu_name, "-merged", NULL);
+
+ system_config_dirs = g_get_system_config_dirs ();
+
+ /* Merge in reverse order */
+ i = 0;
+ while (system_config_dirs[i] != NULL) i++;
+ while (i > 0)
+ {
+ i--;
+ load_merge_dir_with_config_dir (tree,
+ loaded_menu_files,
+ system_config_dirs[i],
+ merge_name,
+ layout);
+ }
+
+ load_merge_dir_with_config_dir (tree,
+ loaded_menu_files,
+ g_get_user_config_dir (),
+ merge_name,
+ layout);
+
+ g_free (merge_name);
+
+ /* remove the now-replaced node */
+ menu_layout_node_unlink (layout);
+}
+
+static void
+add_filename_include (const char *desktop_file_id,
+ DesktopEntry *entry,
+ MenuLayoutNode *include)
+{
+ if (!desktop_entry_has_categories (entry))
+ {
+ MenuLayoutNode *node;
+
+ node = menu_layout_node_new (MENU_LAYOUT_NODE_FILENAME);
+ menu_layout_node_set_content (node, desktop_file_id);
+
+ menu_layout_node_append_child (include, node);
+ menu_layout_node_unref (node);
+ }
+}
+
+static void
+is_dot_directory (const char *basename,
+ DesktopEntry *entry,
+ gboolean *has_dot_directory)
+{
+ if (!strcmp (basename, ".directory"))
+ *has_dot_directory = TRUE;
+}
+
+static gboolean
+add_menu_for_legacy_dir (MenuLayoutNode *parent,
+ const char *legacy_dir,
+ const char *relative_path,
+ const char *legacy_prefix,
+ const char *menu_name)
+{
+ EntryDirectory *ed;
+ DesktopEntrySet *desktop_entries;
+ DesktopEntrySet *directory_entries;
+ GSList *subdirs;
+ gboolean menu_added;
+ gboolean has_dot_directory;
+
+ ed = entry_directory_new_legacy (DESKTOP_ENTRY_INVALID, legacy_dir, legacy_prefix);
+ if (!ed)
+ return FALSE;
+
+ subdirs = NULL;
+ desktop_entries = desktop_entry_set_new ();
+ directory_entries = desktop_entry_set_new ();
+
+ entry_directory_get_flat_contents (ed,
+ desktop_entries,
+ directory_entries,
+ &subdirs);
+ entry_directory_unref (ed);
+
+ has_dot_directory = FALSE;
+ desktop_entry_set_foreach (directory_entries,
+ (DesktopEntrySetForeachFunc) is_dot_directory,
+ &has_dot_directory);
+ desktop_entry_set_unref (directory_entries);
+
+ menu_added = FALSE;
+ if (desktop_entry_set_get_count (desktop_entries) > 0 || subdirs)
+ {
+ MenuLayoutNode *menu;
+ MenuLayoutNode *node;
+ GString *subdir_path;
+ GString *subdir_relative;
+ GSList *tmp;
+ int legacy_dir_len;
+ int relative_path_len;
+
+ menu = menu_layout_node_new (MENU_LAYOUT_NODE_MENU);
+ menu_layout_node_append_child (parent, menu);
+
+ menu_added = TRUE;
+
+ g_assert (menu_name != NULL);
+
+ node = menu_layout_node_new (MENU_LAYOUT_NODE_NAME);
+ menu_layout_node_set_content (node, menu_name);
+ menu_layout_node_append_child (menu, node);
+ menu_layout_node_unref (node);
+
+ if (has_dot_directory)
+ {
+ node = menu_layout_node_new (MENU_LAYOUT_NODE_DIRECTORY);
+ if (relative_path != NULL)
+ {
+ char *directory_entry_path;
+
+ directory_entry_path = g_strdup_printf ("%s/.directory", relative_path);
+ menu_layout_node_set_content (node, directory_entry_path);
+ g_free (directory_entry_path);
+ }
+ else
+ {
+ menu_layout_node_set_content (node, ".directory");
+ }
+ menu_layout_node_append_child (menu, node);
+ menu_layout_node_unref (node);
+ }
+
+ if (desktop_entry_set_get_count (desktop_entries) > 0)
+ {
+ MenuLayoutNode *include;
+
+ include = menu_layout_node_new (MENU_LAYOUT_NODE_INCLUDE);
+ menu_layout_node_append_child (menu, include);
+
+ desktop_entry_set_foreach (desktop_entries,
+ (DesktopEntrySetForeachFunc) add_filename_include,
+ include);
+
+ menu_layout_node_unref (include);
+ }
+
+ subdir_path = g_string_new (legacy_dir);
+ legacy_dir_len = strlen (legacy_dir);
+
+ subdir_relative = g_string_new (relative_path);
+ relative_path_len = relative_path ? strlen (relative_path) : 0;
+
+ tmp = subdirs;
+ while (tmp != NULL)
+ {
+ const char *subdir = tmp->data;
+
+ g_string_append_c (subdir_path, G_DIR_SEPARATOR);
+ g_string_append (subdir_path, subdir);
+
+ if (relative_path_len)
+ {
+ g_string_append_c (subdir_relative, G_DIR_SEPARATOR);
+ }
+ g_string_append (subdir_relative, subdir);
+
+ add_menu_for_legacy_dir (menu,
+ subdir_path->str,
+ subdir_relative->str,
+ legacy_prefix,
+ subdir);
+
+ g_string_truncate (subdir_relative, relative_path_len);
+ g_string_truncate (subdir_path, legacy_dir_len);
+
+ tmp = tmp->next;
+ }
+
+ g_string_free (subdir_path, TRUE);
+ g_string_free (subdir_relative, TRUE);
+
+ menu_layout_node_unref (menu);
+ }
+
+ desktop_entry_set_unref (desktop_entries);
+
+ g_slist_foreach (subdirs, (GFunc) g_free, NULL);
+ g_slist_free (subdirs);
+
+ return menu_added;
+}
+
+static void
+resolve_legacy_dir (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *legacy)
+{
+ MenuLayoutNode *to_merge;
+ MenuLayoutNode *menu;
+
+ to_merge = menu_layout_node_new (MENU_LAYOUT_NODE_ROOT);
+
+ menu = menu_layout_node_get_parent (legacy);
+ g_assert (menu_layout_node_get_type (menu) == MENU_LAYOUT_NODE_MENU);
+
+ if (add_menu_for_legacy_dir (to_merge,
+ menu_layout_node_get_content (legacy),
+ NULL,
+ menu_layout_node_legacy_dir_get_prefix (legacy),
+ menu_layout_node_menu_get_name (menu)))
+ {
+ merge_resolved_children (tree, loaded_menu_files, legacy, to_merge);
+ }
+
+ menu_layout_node_unref (to_merge);
+}
+
+static MenuLayoutNode *
+add_legacy_dir (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *before,
+ const char *data_dir)
+{
+ MenuLayoutNode *legacy;
+ char *dirname;
+
+ dirname = g_build_filename (data_dir, "applnk", NULL);
+
+ legacy = menu_layout_node_new (MENU_LAYOUT_NODE_LEGACY_DIR);
+ menu_layout_node_set_content (legacy, dirname);
+ menu_layout_node_legacy_dir_set_prefix (legacy, "kde");
+ menu_layout_node_insert_before (before, legacy);
+ menu_layout_node_unref (before);
+
+ menu_verbose ("Adding <LegacyDir>%s</LegacyDir> in <KDELegacyDirs/>\n",
+ dirname);
+
+ resolve_legacy_dir (tree, loaded_menu_files, legacy);
+
+ g_free (dirname);
+
+ return legacy;
+}
+
+static void
+resolve_kde_legacy_dirs (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *layout)
+{
+ MenuLayoutNode *before;
+ const char * const *system_data_dirs;
+ int i;
+
+ system_data_dirs = g_get_system_data_dirs ();
+
+ before = add_legacy_dir (tree,
+ loaded_menu_files,
+ menu_layout_node_ref (layout),
+ g_get_user_data_dir ());
+
+ i = 0;
+ while (system_data_dirs[i] != NULL)
+ {
+ before = add_legacy_dir (tree, loaded_menu_files, before, system_data_dirs[i]);
+
+ ++i;
+ }
+
+ menu_layout_node_unref (before);
+
+ /* remove the now-replaced node */
+ menu_layout_node_unlink (layout);
+}
+
+static void
+matemenu_tree_resolve_files (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *layout)
+{
+ MenuLayoutNode *child;
+
+ menu_verbose ("Resolving files in: ");
+ menu_debug_print_layout (layout, TRUE);
+
+ switch (menu_layout_node_get_type (layout))
+ {
+ case MENU_LAYOUT_NODE_MERGE_FILE:
+ resolve_merge_file (tree, loaded_menu_files, layout);
+ break;
+
+ case MENU_LAYOUT_NODE_MERGE_DIR:
+ resolve_merge_dir (tree, loaded_menu_files, layout);
+ break;
+
+ case MENU_LAYOUT_NODE_DEFAULT_APP_DIRS:
+ resolve_default_app_dirs (tree, layout);
+ break;
+
+ case MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS:
+ resolve_default_directory_dirs (tree, layout);
+ break;
+
+ case MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS:
+ resolve_default_merge_dirs (tree, loaded_menu_files, layout);
+ break;
+
+ case MENU_LAYOUT_NODE_LEGACY_DIR:
+ resolve_legacy_dir (tree, loaded_menu_files, layout);
+ break;
+
+ case MENU_LAYOUT_NODE_KDE_LEGACY_DIRS:
+ resolve_kde_legacy_dirs (tree, loaded_menu_files, layout);
+ break;
+
+ case MENU_LAYOUT_NODE_PASSTHROUGH:
+ /* Just get rid of these, we don't need the memory usage */
+ menu_layout_node_unlink (layout);
+ break;
+
+ default:
+ /* Recurse */
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ MenuLayoutNode *next = menu_layout_node_get_next (child);
+
+ matemenu_tree_resolve_files (tree, loaded_menu_files, child);
+
+ child = next;
+ }
+ break;
+ }
+}
+
+static void
+move_children (MenuLayoutNode *from,
+ MenuLayoutNode *to)
+{
+ MenuLayoutNode *from_child;
+ MenuLayoutNode *insert_before;
+
+ insert_before = menu_layout_node_get_children (to);
+ from_child = menu_layout_node_get_children (from);
+
+ while (from_child != NULL)
+ {
+ MenuLayoutNode *next;
+
+ next = menu_layout_node_get_next (from_child);
+
+ menu_layout_node_steal (from_child);
+
+ if (menu_layout_node_get_type (from_child) == MENU_LAYOUT_NODE_NAME)
+ {
+ ; /* just drop the Name in the old <Menu> */
+ }
+ else if (insert_before)
+ {
+ menu_layout_node_insert_before (insert_before, from_child);
+ g_assert (menu_layout_node_get_next (from_child) == insert_before);
+ }
+ else
+ {
+ menu_layout_node_append_child (to, from_child);
+ }
+
+ menu_layout_node_unref (from_child);
+
+ from_child = next;
+ }
+}
+
+static int
+null_safe_strcmp (const char *a,
+ const char *b)
+{
+ if (a == NULL && b == NULL)
+ return 0;
+ else if (a == NULL)
+ return -1;
+ else if (b == NULL)
+ return 1;
+ else
+ return strcmp (a, b);
+}
+
+static int
+node_compare_func (const void *a,
+ const void *b)
+{
+ MenuLayoutNode *node_a = (MenuLayoutNode*) a;
+ MenuLayoutNode *node_b = (MenuLayoutNode*) b;
+ MenuLayoutNodeType t_a = menu_layout_node_get_type (node_a);
+ MenuLayoutNodeType t_b = menu_layout_node_get_type (node_b);
+
+ if (t_a < t_b)
+ return -1;
+ else if (t_a > t_b)
+ return 1;
+ else
+ {
+ const char *c_a = menu_layout_node_get_content (node_a);
+ const char *c_b = menu_layout_node_get_content (node_b);
+
+ return null_safe_strcmp (c_a, c_b);
+ }
+}
+
+static int
+node_menu_compare_func (const void *a,
+ const void *b)
+{
+ MenuLayoutNode *node_a = (MenuLayoutNode*) a;
+ MenuLayoutNode *node_b = (MenuLayoutNode*) b;
+ MenuLayoutNode *parent_a = menu_layout_node_get_parent (node_a);
+ MenuLayoutNode *parent_b = menu_layout_node_get_parent (node_b);
+
+ if (parent_a < parent_b)
+ return -1;
+ else if (parent_a > parent_b)
+ return 1;
+ else
+ return null_safe_strcmp (menu_layout_node_menu_get_name (node_a),
+ menu_layout_node_menu_get_name (node_b));
+}
+
+static void
+matemenu_tree_strip_duplicate_children (MateMenuTree *tree,
+ MenuLayoutNode *layout)
+{
+ MenuLayoutNode *child;
+ GSList *simple_nodes;
+ GSList *menu_layout_nodes;
+ GSList *prev;
+ GSList *tmp;
+
+ /* to strip dups, we find all the child nodes where
+ * we want to kill dups, sort them,
+ * then nuke the adjacent nodes that are equal
+ */
+
+ simple_nodes = NULL;
+ menu_layout_nodes = NULL;
+
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ switch (menu_layout_node_get_type (child))
+ {
+ /* These are dups if their content is the same */
+ case MENU_LAYOUT_NODE_APP_DIR:
+ case MENU_LAYOUT_NODE_DIRECTORY_DIR:
+ case MENU_LAYOUT_NODE_DIRECTORY:
+ simple_nodes = g_slist_prepend (simple_nodes, child);
+ break;
+
+ /* These have to be merged in a more complicated way,
+ * and then recursed
+ */
+ case MENU_LAYOUT_NODE_MENU:
+ menu_layout_nodes = g_slist_prepend (menu_layout_nodes, child);
+ break;
+
+ default:
+ break;
+ }
+
+ child = menu_layout_node_get_next (child);
+ }
+
+ /* Note that the lists are all backward. So we want to keep
+ * the items that are earlier in the list, because they were
+ * later in the file
+ */
+
+ /* stable sort the simple nodes */
+ simple_nodes = g_slist_sort (simple_nodes,
+ node_compare_func);
+
+ prev = NULL;
+ tmp = simple_nodes;
+ while (tmp != NULL)
+ {
+ GSList *next = tmp->next;
+
+ if (prev)
+ {
+ MenuLayoutNode *p = prev->data;
+ MenuLayoutNode *n = tmp->data;
+
+ if (node_compare_func (p, n) == 0)
+ {
+ /* nuke it! */
+ menu_layout_node_unlink (n);
+ simple_nodes = g_slist_delete_link (simple_nodes, tmp);
+ tmp = prev;
+ }
+ }
+
+ prev = tmp;
+ tmp = next;
+ }
+
+ g_slist_free (simple_nodes);
+ simple_nodes = NULL;
+
+ /* stable sort the menu nodes (the sort includes the
+ * parents of the nodes in the comparison). Remember
+ * the list is backward.
+ */
+ menu_layout_nodes = g_slist_sort (menu_layout_nodes,
+ node_menu_compare_func);
+
+ prev = NULL;
+ tmp = menu_layout_nodes;
+ while (tmp != NULL)
+ {
+ GSList *next = tmp->next;
+
+ if (prev)
+ {
+ MenuLayoutNode *p = prev->data;
+ MenuLayoutNode *n = tmp->data;
+
+ if (node_menu_compare_func (p, n) == 0)
+ {
+ /* Move children of first menu to the start of second
+ * menu and nuke the first menu
+ */
+ move_children (n, p);
+ menu_layout_node_unlink (n);
+ menu_layout_nodes = g_slist_delete_link (menu_layout_nodes, tmp);
+ tmp = prev;
+ }
+ }
+
+ prev = tmp;
+ tmp = next;
+ }
+
+ g_slist_free (menu_layout_nodes);
+ menu_layout_nodes = NULL;
+
+ /* Recursively clean up all children */
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ if (menu_layout_node_get_type (child) == MENU_LAYOUT_NODE_MENU)
+ matemenu_tree_strip_duplicate_children (tree, child);
+
+ child = menu_layout_node_get_next (child);
+ }
+}
+
+static MenuLayoutNode *
+find_submenu (MenuLayoutNode *layout,
+ const char *path,
+ gboolean create_if_not_found)
+{
+ MenuLayoutNode *child;
+ const char *slash;
+ const char *next_path;
+ char *name;
+
+ menu_verbose (" (splitting \"%s\")\n", path);
+
+ if (path[0] == '\0' || path[0] == G_DIR_SEPARATOR)
+ return NULL;
+
+ slash = strchr (path, G_DIR_SEPARATOR);
+ if (slash != NULL)
+ {
+ name = g_strndup (path, slash - path);
+ next_path = slash + 1;
+ if (*next_path == '\0')
+ next_path = NULL;
+ }
+ else
+ {
+ name = g_strdup (path);
+ next_path = NULL;
+ }
+
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ switch (menu_layout_node_get_type (child))
+ {
+ case MENU_LAYOUT_NODE_MENU:
+ {
+ if (strcmp (name, menu_layout_node_menu_get_name (child)) == 0)
+ {
+ menu_verbose ("MenuNode %p found for path component \"%s\"\n",
+ child, name);
+
+ g_free (name);
+
+ if (!next_path)
+ {
+ menu_verbose (" Found menu node %p parent is %p\n",
+ child, layout);
+ return child;
+ }
+
+ return find_submenu (child, next_path, create_if_not_found);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ child = menu_layout_node_get_next (child);
+ }
+
+ if (create_if_not_found)
+ {
+ MenuLayoutNode *name_node;
+
+ child = menu_layout_node_new (MENU_LAYOUT_NODE_MENU);
+ menu_layout_node_append_child (layout, child);
+
+ name_node = menu_layout_node_new (MENU_LAYOUT_NODE_NAME);
+ menu_layout_node_set_content (name_node, name);
+ menu_layout_node_append_child (child, name_node);
+ menu_layout_node_unref (name_node);
+
+ menu_verbose (" Created menu node %p parent is %p\n",
+ child, layout);
+
+ menu_layout_node_unref (child);
+ g_free (name);
+
+ if (!next_path)
+ return child;
+
+ return find_submenu (child, next_path, create_if_not_found);
+ }
+ else
+ {
+ g_free (name);
+ return NULL;
+ }
+}
+
+/* To call this you first have to strip duplicate children once,
+ * otherwise when you move a menu Foo to Bar then you may only
+ * move one of Foo, not all the merged Foo.
+ */
+static void
+matemenu_tree_execute_moves (MateMenuTree *tree,
+ MenuLayoutNode *layout,
+ gboolean *need_remove_dups_p)
+{
+ MenuLayoutNode *child;
+ gboolean need_remove_dups;
+ GSList *move_nodes;
+ GSList *tmp;
+
+ need_remove_dups = FALSE;
+
+ move_nodes = NULL;
+
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ switch (menu_layout_node_get_type (child))
+ {
+ case MENU_LAYOUT_NODE_MENU:
+ /* Recurse - we recurse first and process the current node
+ * second, as the spec dictates.
+ */
+ matemenu_tree_execute_moves (tree, child, &need_remove_dups);
+ break;
+
+ case MENU_LAYOUT_NODE_MOVE:
+ move_nodes = g_slist_prepend (move_nodes, child);
+ break;
+
+ default:
+ break;
+ }
+
+ child = menu_layout_node_get_next (child);
+ }
+
+ /* We need to execute the move operations in the order that they appear */
+ move_nodes = g_slist_reverse (move_nodes);
+
+ tmp = move_nodes;
+ while (tmp != NULL)
+ {
+ MenuLayoutNode *move_node = tmp->data;
+ MenuLayoutNode *old_node;
+ GSList *next = tmp->next;
+ const char *old;
+ const char *new;
+
+ old = menu_layout_node_move_get_old (move_node);
+ new = menu_layout_node_move_get_new (move_node);
+ g_assert (old != NULL && new != NULL);
+
+ menu_verbose ("executing <Move> old = \"%s\" new = \"%s\"\n",
+ old, new);
+
+ old_node = find_submenu (layout, old, FALSE);
+ if (old_node != NULL)
+ {
+ MenuLayoutNode *new_node;
+
+ /* here we can create duplicates anywhere below the
+ * node
+ */
+ need_remove_dups = TRUE;
+
+ /* look up new node creating it and its parents if
+ * required
+ */
+ new_node = find_submenu (layout, new, TRUE);
+ g_assert (new_node != NULL);
+
+ move_children (old_node, new_node);
+
+ menu_layout_node_unlink (old_node);
+ }
+
+ menu_layout_node_unlink (move_node);
+
+ tmp = next;
+ }
+
+ g_slist_free (move_nodes);
+
+ /* This oddness is to ensure we only remove dups once,
+ * at the root, instead of recursing the tree over
+ * and over.
+ */
+ if (need_remove_dups_p)
+ *need_remove_dups_p = need_remove_dups;
+ else if (need_remove_dups)
+ matemenu_tree_strip_duplicate_children (tree, layout);
+}
+
+static void
+matemenu_tree_load_layout (MateMenuTree *tree)
+{
+ GHashTable *loaded_menu_files;
+ GError *error;
+
+ if (tree->layout)
+ return;
+
+ if (!matemenu_tree_canonicalize_path (tree))
+ return;
+
+ menu_verbose ("Loading menu layout from \"%s\"\n",
+ tree->canonical_path);
+
+ error = NULL;
+ tree->layout = menu_layout_load (tree->canonical_path,
+ tree->type == MATEMENU_TREE_BASENAME ?
+ tree->basename : NULL,
+ &error);
+ if (tree->layout == NULL)
+ {
+ g_warning ("Error loading menu layout from \"%s\": %s",
+ tree->canonical_path, error->message);
+ g_error_free (error);
+ return;
+ }
+
+ loaded_menu_files = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (loaded_menu_files, tree->canonical_path, GUINT_TO_POINTER (TRUE));
+ matemenu_tree_resolve_files (tree, loaded_menu_files, tree->layout);
+ g_hash_table_destroy (loaded_menu_files);
+
+ matemenu_tree_strip_duplicate_children (tree, tree->layout);
+ matemenu_tree_execute_moves (tree, tree->layout, NULL);
+}
+
+static void
+matemenu_tree_force_reload (MateMenuTree *tree)
+{
+ matemenu_tree_force_rebuild (tree);
+
+ if (tree->layout)
+ menu_layout_node_unref (tree->layout);
+ tree->layout = NULL;
+}
+
+typedef struct
+{
+ DesktopEntrySet *set;
+ const char *category;
+} GetByCategoryForeachData;
+
+static void
+get_by_category_foreach (const char *file_id,
+ DesktopEntry *entry,
+ GetByCategoryForeachData *data)
+{
+ if (desktop_entry_has_category (entry, data->category))
+ desktop_entry_set_add_entry (data->set, entry, file_id);
+}
+
+static void
+get_by_category (DesktopEntrySet *entry_pool,
+ DesktopEntrySet *set,
+ const char *category)
+{
+ GetByCategoryForeachData data;
+
+ data.set = set;
+ data.category = category;
+
+ desktop_entry_set_foreach (entry_pool,
+ (DesktopEntrySetForeachFunc) get_by_category_foreach,
+ &data);
+}
+
+static DesktopEntrySet *
+process_include_rules (MenuLayoutNode *layout,
+ DesktopEntrySet *entry_pool)
+{
+ DesktopEntrySet *set = NULL;
+
+ switch (menu_layout_node_get_type (layout))
+ {
+ case MENU_LAYOUT_NODE_AND:
+ {
+ MenuLayoutNode *child;
+
+ menu_verbose ("Processing <And>\n");
+
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ DesktopEntrySet *child_set;
+
+ child_set = process_include_rules (child, entry_pool);
+
+ if (set == NULL)
+ {
+ set = child_set;
+ }
+ else
+ {
+ desktop_entry_set_intersection (set, child_set);
+ desktop_entry_set_unref (child_set);
+ }
+
+ /* as soon as we get empty results, we can bail,
+ * because it's an AND
+ */
+ if (desktop_entry_set_get_count (set) == 0)
+ break;
+
+ child = menu_layout_node_get_next (child);
+ }
+ menu_verbose ("Processed <And>\n");
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_OR:
+ {
+ MenuLayoutNode *child;
+
+ menu_verbose ("Processing <Or>\n");
+
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ DesktopEntrySet *child_set;
+
+ child_set = process_include_rules (child, entry_pool);
+
+ if (set == NULL)
+ {
+ set = child_set;
+ }
+ else
+ {
+ desktop_entry_set_union (set, child_set);
+ desktop_entry_set_unref (child_set);
+ }
+
+ child = menu_layout_node_get_next (child);
+ }
+ menu_verbose ("Processed <Or>\n");
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_NOT:
+ {
+ /* First get the OR of all the rules */
+ MenuLayoutNode *child;
+
+ menu_verbose ("Processing <Not>\n");
+
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ DesktopEntrySet *child_set;
+
+ child_set = process_include_rules (child, entry_pool);
+
+ if (set == NULL)
+ {
+ set = child_set;
+ }
+ else
+ {
+ desktop_entry_set_union (set, child_set);
+ desktop_entry_set_unref (child_set);
+ }
+
+ child = menu_layout_node_get_next (child);
+ }
+
+ if (set != NULL)
+ {
+ DesktopEntrySet *inverted;
+
+ /* Now invert the result */
+ inverted = desktop_entry_set_new ();
+ desktop_entry_set_union (inverted, entry_pool);
+ desktop_entry_set_subtract (inverted, set);
+ desktop_entry_set_unref (set);
+ set = inverted;
+ }
+ menu_verbose ("Processed <Not>\n");
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_ALL:
+ menu_verbose ("Processing <All>\n");
+ set = desktop_entry_set_new ();
+ desktop_entry_set_union (set, entry_pool);
+ menu_verbose ("Processed <All>\n");
+ break;
+
+ case MENU_LAYOUT_NODE_FILENAME:
+ {
+ DesktopEntry *entry;
+
+ menu_verbose ("Processing <Filename>%s</Filename>\n",
+ menu_layout_node_get_content (layout));
+
+ entry = desktop_entry_set_lookup (entry_pool,
+ menu_layout_node_get_content (layout));
+ if (entry != NULL)
+ {
+ set = desktop_entry_set_new ();
+ desktop_entry_set_add_entry (set,
+ entry,
+ menu_layout_node_get_content (layout));
+ }
+ menu_verbose ("Processed <Filename>%s</Filename>\n",
+ menu_layout_node_get_content (layout));
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_CATEGORY:
+ menu_verbose ("Processing <Category>%s</Category>\n",
+ menu_layout_node_get_content (layout));
+ set = desktop_entry_set_new ();
+ get_by_category (entry_pool, set, menu_layout_node_get_content (layout));
+ menu_verbose ("Processed <Category>%s</Category>\n",
+ menu_layout_node_get_content (layout));
+ break;
+
+ default:
+ break;
+ }
+
+ if (set == NULL)
+ set = desktop_entry_set_new (); /* create an empty set */
+
+ menu_verbose ("Matched %d entries\n", desktop_entry_set_get_count (set));
+
+ return set;
+}
+
+static void
+collect_layout_info (MenuLayoutNode *layout,
+ GSList **layout_info)
+{
+ MenuLayoutNode *iter;
+
+ g_slist_foreach (*layout_info,
+ (GFunc) menu_layout_node_unref,
+ NULL);
+ g_slist_free (*layout_info);
+ *layout_info = NULL;
+
+ iter = menu_layout_node_get_children (layout);
+ while (iter != NULL)
+ {
+ switch (menu_layout_node_get_type (iter))
+ {
+ case MENU_LAYOUT_NODE_MENUNAME:
+ case MENU_LAYOUT_NODE_FILENAME:
+ case MENU_LAYOUT_NODE_SEPARATOR:
+ case MENU_LAYOUT_NODE_MERGE:
+ *layout_info = g_slist_prepend (*layout_info,
+ menu_layout_node_ref (iter));
+ break;
+
+ default:
+ break;
+ }
+
+ iter = menu_layout_node_get_next (iter);
+ }
+
+ *layout_info = g_slist_reverse (*layout_info);
+}
+
+static void
+entries_listify_foreach (const char *desktop_file_id,
+ DesktopEntry *desktop_entry,
+ MateMenuTreeDirectory *directory)
+{
+ directory->entries =
+ g_slist_prepend (directory->entries,
+ matemenu_tree_entry_new (directory,
+ desktop_entry,
+ desktop_file_id,
+ FALSE,
+ desktop_entry_get_no_display (desktop_entry)));
+}
+
+static void
+excluded_entries_listify_foreach (const char *desktop_file_id,
+ DesktopEntry *desktop_entry,
+ MateMenuTreeDirectory *directory)
+{
+ directory->entries =
+ g_slist_prepend (directory->entries,
+ matemenu_tree_entry_new (directory,
+ desktop_entry,
+ desktop_file_id,
+ TRUE,
+ desktop_entry_get_no_display (desktop_entry)));
+}
+
+static void
+set_default_layout_values (MateMenuTreeDirectory *parent,
+ MateMenuTreeDirectory *child)
+{
+ GSList *tmp;
+
+ /* if the child has a defined default layout, we don't want to override its
+ * values. The parent might have a non-defined layout info (ie, no child of
+ * the DefaultLayout node) but it doesn't meant the default layout values
+ * (ie, DefaultLayout attributes) aren't different from the global defaults.
+ */
+ if (child->default_layout_info != NULL ||
+ child->default_layout_values.mask != MENU_LAYOUT_VALUES_NONE)
+ return;
+
+ child->default_layout_values = parent->default_layout_values;
+
+ tmp = child->subdirs;
+ while (tmp != NULL)
+ {
+ MateMenuTreeDirectory *subdir = tmp->data;
+
+ set_default_layout_values (child, subdir);
+
+ tmp = tmp->next;
+ }
+}
+
+static MateMenuTreeDirectory *
+process_layout (MateMenuTree *tree,
+ MateMenuTreeDirectory *parent,
+ MenuLayoutNode *layout,
+ DesktopEntrySet *allocated)
+{
+ MenuLayoutNode *layout_iter;
+ MateMenuTreeDirectory *directory;
+ DesktopEntrySet *entry_pool;
+ DesktopEntrySet *entries;
+ DesktopEntrySet *allocated_set;
+ DesktopEntrySet *excluded_set;
+ gboolean deleted;
+ gboolean only_unallocated;
+ GSList *tmp;
+
+ g_assert (menu_layout_node_get_type (layout) == MENU_LAYOUT_NODE_MENU);
+ g_assert (menu_layout_node_menu_get_name (layout) != NULL);
+
+ directory = matemenu_tree_directory_new (parent,
+ menu_layout_node_menu_get_name (layout),
+ parent == NULL);
+
+ menu_verbose ("=== Menu name = %s ===\n", directory->name);
+
+
+ deleted = FALSE;
+ only_unallocated = FALSE;
+
+ entries = desktop_entry_set_new ();
+ allocated_set = desktop_entry_set_new ();
+
+ if (tree->flags & MATEMENU_TREE_FLAGS_INCLUDE_EXCLUDED)
+ excluded_set = desktop_entry_set_new ();
+ else
+ excluded_set = NULL;
+
+ entry_pool = _entry_directory_list_get_all_desktops (menu_layout_node_menu_get_app_dirs (layout));
+
+ layout_iter = menu_layout_node_get_children (layout);
+ while (layout_iter != NULL)
+ {
+ switch (menu_layout_node_get_type (layout_iter))
+ {
+ case MENU_LAYOUT_NODE_MENU:
+ /* recurse */
+ {
+ MateMenuTreeDirectory *child_dir;
+
+ menu_verbose ("Processing <Menu>\n");
+
+ child_dir = process_layout (tree,
+ directory,
+ layout_iter,
+ allocated);
+ if (child_dir)
+ directory->subdirs = g_slist_prepend (directory->subdirs,
+ child_dir);
+
+ menu_verbose ("Processed <Menu>\n");
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_INCLUDE:
+ {
+ /* The match rule children of the <Include> are
+ * independent (logical OR) so we can process each one by
+ * itself
+ */
+ MenuLayoutNode *rule;
+
+ menu_verbose ("Processing <Include> (%d entries)\n",
+ desktop_entry_set_get_count (entries));
+
+ rule = menu_layout_node_get_children (layout_iter);
+ while (rule != NULL)
+ {
+ DesktopEntrySet *rule_set;
+
+ rule_set = process_include_rules (rule, entry_pool);
+ if (rule_set != NULL)
+ {
+ desktop_entry_set_union (entries, rule_set);
+ desktop_entry_set_union (allocated_set, rule_set);
+ if (excluded_set != NULL)
+ desktop_entry_set_subtract (excluded_set, rule_set);
+ desktop_entry_set_unref (rule_set);
+ }
+
+ rule = menu_layout_node_get_next (rule);
+ }
+
+ menu_verbose ("Processed <Include> (%d entries)\n",
+ desktop_entry_set_get_count (entries));
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_EXCLUDE:
+ {
+ /* The match rule children of the <Exclude> are
+ * independent (logical OR) so we can process each one by
+ * itself
+ */
+ MenuLayoutNode *rule;
+
+ menu_verbose ("Processing <Exclude> (%d entries)\n",
+ desktop_entry_set_get_count (entries));
+
+ rule = menu_layout_node_get_children (layout_iter);
+ while (rule != NULL)
+ {
+ DesktopEntrySet *rule_set;
+
+ rule_set = process_include_rules (rule, entry_pool);
+ if (rule_set != NULL)
+ {
+ if (excluded_set != NULL)
+ desktop_entry_set_union (excluded_set, rule_set);
+ desktop_entry_set_subtract (entries, rule_set);
+ desktop_entry_set_unref (rule_set);
+ }
+
+ rule = menu_layout_node_get_next (rule);
+ }
+
+ menu_verbose ("Processed <Exclude> (%d entries)\n",
+ desktop_entry_set_get_count (entries));
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_DIRECTORY:
+ {
+ DesktopEntry *entry;
+
+ menu_verbose ("Processing <Directory>%s</Directory>\n",
+ menu_layout_node_get_content (layout_iter));
+
+ /*
+ * The last <Directory> to exist wins, so we always try overwriting
+ */
+ entry = entry_directory_list_get_directory (menu_layout_node_menu_get_directory_dirs (layout),
+ menu_layout_node_get_content (layout_iter));
+
+ if (entry != NULL)
+ {
+ if (!desktop_entry_get_hidden (entry))
+ {
+ if (directory->directory_entry)
+ desktop_entry_unref (directory->directory_entry);
+ directory->directory_entry = entry; /* pass ref ownership */
+ }
+ else
+ {
+ desktop_entry_unref (entry);
+ }
+ }
+
+ menu_verbose ("Processed <Directory> new directory entry = %p (%s)\n",
+ directory->directory_entry,
+ directory->directory_entry? desktop_entry_get_path (directory->directory_entry) : "null");
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_DELETED:
+ menu_verbose ("Processed <Deleted/>\n");
+ deleted = TRUE;
+ break;
+
+ case MENU_LAYOUT_NODE_NOT_DELETED:
+ menu_verbose ("Processed <NotDeleted/>\n");
+ deleted = FALSE;
+ break;
+
+ case MENU_LAYOUT_NODE_ONLY_UNALLOCATED:
+ menu_verbose ("Processed <OnlyUnallocated/>\n");
+ only_unallocated = TRUE;
+ break;
+
+ case MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED:
+ menu_verbose ("Processed <NotOnlyUnallocated/>\n");
+ only_unallocated = FALSE;
+ break;
+
+ case MENU_LAYOUT_NODE_DEFAULT_LAYOUT:
+ menu_layout_node_default_layout_get_values (layout_iter,
+ &directory->default_layout_values);
+ collect_layout_info (layout_iter, &directory->default_layout_info);
+ menu_verbose ("Processed <DefaultLayout/>\n");
+ break;
+
+ case MENU_LAYOUT_NODE_LAYOUT:
+ collect_layout_info (layout_iter, &directory->layout_info);
+ menu_verbose ("Processed <Layout/>\n");
+ break;
+
+ default:
+ break;
+ }
+
+ layout_iter = menu_layout_node_get_next (layout_iter);
+ }
+
+ desktop_entry_set_unref (entry_pool);
+
+ directory->only_unallocated = only_unallocated;
+
+ if (!directory->only_unallocated)
+ desktop_entry_set_union (allocated, allocated_set);
+
+ desktop_entry_set_unref (allocated_set);
+
+ if (directory->directory_entry)
+ {
+ if (desktop_entry_get_no_display (directory->directory_entry))
+ {
+ directory->is_nodisplay = TRUE;
+
+ if (!(tree->flags & MATEMENU_TREE_FLAGS_INCLUDE_NODISPLAY))
+ {
+ menu_verbose ("Not showing menu %s because NoDisplay=true\n",
+ desktop_entry_get_name (directory->directory_entry));
+ deleted = TRUE;
+ }
+ }
+
+ if (!desktop_entry_get_show_in_mate (directory->directory_entry))
+ {
+ menu_verbose ("Not showing menu %s because OnlyShowIn!=MATE or NotShowIn=MATE\n",
+ desktop_entry_get_name (directory->directory_entry));
+ deleted = TRUE;
+ }
+ }
+
+ if (deleted)
+ {
+ if (excluded_set != NULL)
+ desktop_entry_set_unref (excluded_set);
+ desktop_entry_set_unref (entries);
+ matemenu_tree_item_unref (directory);
+ return NULL;
+ }
+
+ desktop_entry_set_foreach (entries,
+ (DesktopEntrySetForeachFunc) entries_listify_foreach,
+ directory);
+ desktop_entry_set_unref (entries);
+
+ if (excluded_set != NULL)
+ {
+ desktop_entry_set_foreach (excluded_set,
+ (DesktopEntrySetForeachFunc) excluded_entries_listify_foreach,
+ directory);
+ desktop_entry_set_unref (excluded_set);
+ }
+
+ tmp = directory->subdirs;
+ while (tmp != NULL)
+ {
+ MateMenuTreeDirectory *subdir = tmp->data;
+
+ set_default_layout_values (directory, subdir);
+
+ tmp = tmp->next;
+ }
+
+ tmp = directory->entries;
+ while (tmp != NULL)
+ {
+ MateMenuTreeEntry *entry = tmp->data;
+ GSList *next = tmp->next;
+ gboolean delete = FALSE;
+
+ if (desktop_entry_get_hidden (entry->desktop_entry))
+ {
+ menu_verbose ("Deleting %s because Hidden=true\n",
+ desktop_entry_get_name (entry->desktop_entry));
+ delete = TRUE;
+ }
+
+ if (!(tree->flags & MATEMENU_TREE_FLAGS_INCLUDE_NODISPLAY) &&
+ desktop_entry_get_no_display (entry->desktop_entry))
+ {
+ menu_verbose ("Deleting %s because NoDisplay=true\n",
+ desktop_entry_get_name (entry->desktop_entry));
+ delete = TRUE;
+ }
+
+ if (!desktop_entry_get_show_in_mate (entry->desktop_entry))
+ {
+ menu_verbose ("Deleting %s because OnlyShowIn!=MATE or NotShowIn=MATE\n",
+ desktop_entry_get_name (entry->desktop_entry));
+ delete = TRUE;
+ }
+
+ if (desktop_entry_get_tryexec_failed (entry->desktop_entry))
+ {
+ menu_verbose ("Deleting %s because TryExec failed\n",
+ desktop_entry_get_name (entry->desktop_entry));
+ delete = TRUE;
+ }
+
+ if (delete)
+ {
+ directory->entries = g_slist_delete_link (directory->entries,
+ tmp);
+ matemenu_tree_item_unref_and_unset_parent (entry);
+ }
+
+ tmp = next;
+ }
+
+ g_assert (directory->name != NULL);
+
+ return directory;
+}
+
+static void
+process_only_unallocated (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ DesktopEntrySet *allocated)
+{
+ GSList *tmp;
+
+ /* For any directory marked only_unallocated, we have to remove any
+ * entries that were in fact allocated.
+ */
+
+ if (directory->only_unallocated)
+ {
+ tmp = directory->entries;
+ while (tmp != NULL)
+ {
+ MateMenuTreeEntry *entry = tmp->data;
+ GSList *next = tmp->next;
+
+ if (desktop_entry_set_lookup (allocated, entry->desktop_file_id))
+ {
+ directory->entries = g_slist_delete_link (directory->entries,
+ tmp);
+ matemenu_tree_item_unref_and_unset_parent (entry);
+ }
+
+ tmp = next;
+ }
+ }
+
+ tmp = directory->subdirs;
+ while (tmp != NULL)
+ {
+ MateMenuTreeDirectory *subdir = tmp->data;
+
+ process_only_unallocated (tree, subdir, allocated);
+
+ tmp = tmp->next;
+ }
+}
+
+static void preprocess_layout_info (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory);
+
+static GSList *
+get_layout_info (MateMenuTreeDirectory *directory,
+ gboolean *is_default_layout)
+{
+ MateMenuTreeDirectory *iter;
+
+ if (directory->layout_info != NULL)
+ {
+ if (is_default_layout)
+ {
+ *is_default_layout = FALSE;
+ }
+ return directory->layout_info;
+ }
+
+ /* Even if there's no layout information at all, the result will be an
+ * implicit default layout */
+ if (is_default_layout)
+ {
+ *is_default_layout = TRUE;
+ }
+
+ iter = directory;
+ while (iter != NULL)
+ {
+ /* FIXME: this is broken: we might skip real parent in the
+ * XML structure, that are hidden because of inlining. */
+ if (iter->default_layout_info != NULL)
+ {
+ return iter->default_layout_info;
+ }
+
+ iter = MATEMENU_TREE_ITEM (iter)->parent;
+ }
+
+ return NULL;
+}
+
+static void
+get_values_with_defaults (MenuLayoutNode *node,
+ MenuLayoutValues *layout_values,
+ MenuLayoutValues *default_layout_values)
+{
+ menu_layout_node_menuname_get_values (node, layout_values);
+
+ if (!(layout_values->mask & MENU_LAYOUT_VALUES_SHOW_EMPTY))
+ layout_values->show_empty = default_layout_values->show_empty;
+
+ if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_MENUS))
+ layout_values->inline_menus = default_layout_values->inline_menus;
+
+ if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_LIMIT))
+ layout_values->inline_limit = default_layout_values->inline_limit;
+
+ if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_HEADER))
+ layout_values->inline_header = default_layout_values->inline_header;
+
+ if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_ALIAS))
+ layout_values->inline_alias = default_layout_values->inline_alias;
+}
+
+static guint
+get_real_subdirs_len (MateMenuTreeDirectory *directory)
+{
+ guint len;
+ GSList *tmp;
+
+ len = 0;
+
+ tmp = directory->subdirs;
+ while (tmp != NULL)
+ {
+ MateMenuTreeDirectory *subdir = tmp->data;
+
+ tmp = tmp->next;
+
+ if (subdir->will_inline_header != G_MAXUINT16)
+ {
+ len += get_real_subdirs_len (subdir) + g_slist_length (subdir->entries) + 1;
+ }
+ else
+ len += 1;
+ }
+
+ return len;
+}
+
+static void
+preprocess_layout_info_subdir_helper (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ MateMenuTreeDirectory *subdir,
+ MenuLayoutValues *layout_values,
+ gboolean *contents_added,
+ gboolean *should_remove)
+{
+ preprocess_layout_info (tree, subdir);
+
+ *should_remove = FALSE;
+ *contents_added = FALSE;
+
+ if (subdir->subdirs == NULL && subdir->entries == NULL)
+ {
+ if (!(tree->flags & MATEMENU_TREE_FLAGS_SHOW_EMPTY) &&
+ !layout_values->show_empty)
+ {
+ menu_verbose ("Not showing empty menu '%s'\n", subdir->name);
+ *should_remove = TRUE;
+ }
+ }
+
+ else if (layout_values->inline_menus)
+ {
+ guint real_subdirs_len;
+
+ real_subdirs_len = get_real_subdirs_len (subdir);
+
+ if (layout_values->inline_alias &&
+ real_subdirs_len + g_slist_length (subdir->entries) == 1)
+ {
+ MateMenuTreeAlias *alias;
+ MateMenuTreeItem *item;
+ GSList *list;
+
+ if (subdir->subdirs != NULL)
+ list = subdir->subdirs;
+ else
+ list = subdir->entries;
+
+ item = MATEMENU_TREE_ITEM (list->data);
+
+ menu_verbose ("Inline aliasing '%s' to '%s'\n",
+ item->type == MATEMENU_TREE_ITEM_ENTRY ?
+ matemenu_tree_entry_get_name (MATEMENU_TREE_ENTRY (item)) :
+ (item->type == MATEMENU_TREE_ITEM_DIRECTORY ?
+ matemenu_tree_directory_get_name (MATEMENU_TREE_DIRECTORY (item)) :
+ matemenu_tree_directory_get_name (MATEMENU_TREE_ALIAS (item)->directory)),
+ subdir->name);
+
+ alias = matemenu_tree_alias_new (directory, subdir, item);
+
+ g_slist_foreach (list,
+ (GFunc) matemenu_tree_item_unref_and_unset_parent,
+ NULL);
+ g_slist_free (list);
+ subdir->subdirs = NULL;
+ subdir->entries = NULL;
+
+ if (item->type == MATEMENU_TREE_ITEM_DIRECTORY)
+ directory->subdirs = g_slist_append (directory->subdirs, alias);
+ else
+ directory->entries = g_slist_append (directory->entries, alias);
+
+ *contents_added = TRUE;
+ *should_remove = TRUE;
+ }
+
+ else if (layout_values->inline_limit == 0 ||
+ layout_values->inline_limit >= real_subdirs_len + g_slist_length (subdir->entries))
+ {
+ if (layout_values->inline_header)
+ {
+ menu_verbose ("Creating inline header with name '%s'\n", subdir->name);
+ /* we're limited to 16-bits to spare some memory; if the limit is
+ * higher than that (would be crazy), we just consider it's
+ * unlimited */
+ if (layout_values->inline_limit < G_MAXUINT16)
+ subdir->will_inline_header = layout_values->inline_limit;
+ else
+ subdir->will_inline_header = 0;
+ }
+ else
+ {
+ g_slist_foreach (subdir->subdirs,
+ (GFunc) matemenu_tree_item_set_parent,
+ directory);
+ directory->subdirs = g_slist_concat (directory->subdirs,
+ subdir->subdirs);
+ subdir->subdirs = NULL;
+
+ g_slist_foreach (subdir->entries,
+ (GFunc) matemenu_tree_item_set_parent,
+ directory);
+ directory->entries = g_slist_concat (directory->entries,
+ subdir->entries);
+ subdir->entries = NULL;
+
+ *contents_added = TRUE;
+ *should_remove = TRUE;
+ }
+
+ menu_verbose ("Inlining directory contents of '%s' to '%s'\n",
+ subdir->name, directory->name);
+ }
+ }
+}
+
+static void
+preprocess_layout_info (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory)
+{
+ GSList *tmp;
+ GSList *layout_info;
+ gboolean using_default_layout;
+ GSList *last_subdir;
+ gboolean strip_duplicates;
+ gboolean contents_added;
+ gboolean should_remove;
+ GSList *subdirs_sentinel;
+
+ /* Note: we need to preprocess all menus, even if the layout mask for a menu
+ * is MENU_LAYOUT_VALUES_NONE: in this case, we need to remove empty menus;
+ * and the layout mask can be different for a submenu anyway */
+
+ menu_verbose ("Processing menu layout inline hints for %s\n", directory->name);
+ g_assert (!directory->preprocessed);
+
+ strip_duplicates = FALSE;
+ /* we use last_subdir to track the last non-inlined subdirectory */
+ last_subdir = g_slist_last (directory->subdirs);
+
+ /*
+ * First process subdirectories with explicit layout
+ */
+ layout_info = get_layout_info (directory, &using_default_layout);
+ tmp = layout_info;
+ /* see comment below about Menuname to understand why we leave the loop if
+ * last_subdir is NULL */
+ while (tmp != NULL && last_subdir != NULL)
+ {
+ MenuLayoutNode *node = tmp->data;
+ MenuLayoutValues layout_values;
+ const char *name;
+ MateMenuTreeDirectory *subdir;
+ GSList *subdir_l;
+
+ tmp = tmp->next;
+
+ /* only Menuname nodes are relevant here */
+ if (menu_layout_node_get_type (node) != MENU_LAYOUT_NODE_MENUNAME)
+ continue;
+
+ get_values_with_defaults (node,
+ &layout_values,
+ &directory->default_layout_values);
+
+ /* find the subdirectory that is affected by those attributes */
+ name = menu_layout_node_get_content (node);
+ subdir = NULL;
+ subdir_l = directory->subdirs;
+ while (subdir_l != NULL)
+ {
+ subdir = subdir_l->data;
+
+ if (!strcmp (subdir->name, name))
+ break;
+
+ subdir = NULL;
+ subdir_l = subdir_l->next;
+
+ /* We do not want to use Menuname on a menu that appeared via
+ * inlining: without inlining, the Menuname wouldn't have matched
+ * anything, and we want to keep the same behavior.
+ * Unless the layout is a default layout, in which case the Menuname
+ * does match the subdirectory. */
+ if (!using_default_layout && subdir_l == last_subdir)
+ {
+ subdir_l = NULL;
+ break;
+ }
+ }
+
+ if (subdir == NULL)
+ continue;
+
+ preprocess_layout_info_subdir_helper (tree, directory,
+ subdir, &layout_values,
+ &contents_added, &should_remove);
+ strip_duplicates = strip_duplicates || contents_added;
+ if (should_remove)
+ {
+ if (last_subdir == subdir_l)
+ {
+ /* we need to recompute last_subdir since we'll remove it from
+ * the list */
+ GSList *buf;
+
+ if (subdir_l == directory->subdirs)
+ last_subdir = NULL;
+ else
+ {
+ buf = directory->subdirs;
+ while (buf != NULL && buf->next != subdir_l)
+ buf = buf->next;
+ last_subdir = buf;
+ }
+ }
+
+ directory->subdirs = g_slist_remove (directory->subdirs, subdir);
+ matemenu_tree_item_unref_and_unset_parent (MATEMENU_TREE_ITEM (subdir));
+ }
+ }
+
+ /*
+ * Now process the subdirectories with no explicit layout
+ */
+ /* this is bogus data, but we just need the pointer anyway */
+ subdirs_sentinel = g_slist_prepend (directory->subdirs, PACKAGE);
+ directory->subdirs = subdirs_sentinel;
+
+ tmp = directory->subdirs;
+ while (tmp->next != NULL)
+ {
+ MateMenuTreeDirectory *subdir = tmp->next->data;
+
+ if (subdir->preprocessed)
+ {
+ tmp = tmp->next;
+ continue;
+ }
+
+ preprocess_layout_info_subdir_helper (tree, directory,
+ subdir, &directory->default_layout_values,
+ &contents_added, &should_remove);
+ strip_duplicates = strip_duplicates || contents_added;
+ if (should_remove)
+ {
+ tmp = g_slist_delete_link (tmp, tmp->next);
+ matemenu_tree_item_unref_and_unset_parent (MATEMENU_TREE_ITEM (subdir));
+ }
+ else
+ tmp = tmp->next;
+ }
+
+ /* remove the sentinel */
+ directory->subdirs = g_slist_delete_link (directory->subdirs,
+ directory->subdirs);
+
+ /*
+ * Finally, remove duplicates if needed
+ */
+ if (strip_duplicates)
+ {
+ /* strip duplicate entries; there should be no duplicate directories */
+ directory->entries = g_slist_sort (directory->entries,
+ (GCompareFunc) matemenu_tree_entry_compare_by_id);
+ tmp = directory->entries;
+ while (tmp != NULL && tmp->next != NULL)
+ {
+ MateMenuTreeItem *a = tmp->data;
+ MateMenuTreeItem *b = tmp->next->data;
+
+ if (a->type == MATEMENU_TREE_ITEM_ALIAS)
+ a = MATEMENU_TREE_ALIAS (a)->aliased_item;
+
+ if (b->type == MATEMENU_TREE_ITEM_ALIAS)
+ b = MATEMENU_TREE_ALIAS (b)->aliased_item;
+
+ if (strcmp (MATEMENU_TREE_ENTRY (a)->desktop_file_id,
+ MATEMENU_TREE_ENTRY (b)->desktop_file_id) == 0)
+ {
+ tmp = g_slist_delete_link (tmp, tmp->next);
+ matemenu_tree_item_unref (b);
+ }
+ else
+ tmp = tmp->next;
+ }
+ }
+
+ directory->preprocessed = TRUE;
+}
+
+static void process_layout_info (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory);
+
+static void
+check_pending_separator (MateMenuTreeDirectory *directory)
+{
+ if (directory->layout_pending_separator)
+ {
+ menu_verbose ("Adding pending separator in '%s'\n", directory->name);
+
+ directory->contents = g_slist_append (directory->contents,
+ matemenu_tree_separator_new (directory));
+ directory->layout_pending_separator = FALSE;
+ }
+}
+
+static void
+merge_alias (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ MateMenuTreeAlias *alias)
+{
+ menu_verbose ("Merging alias '%s' in directory '%s'\n",
+ alias->directory->name, directory->name);
+
+ if (alias->aliased_item->type == MATEMENU_TREE_ITEM_DIRECTORY)
+ {
+ process_layout_info (tree, MATEMENU_TREE_DIRECTORY (alias->aliased_item));
+ }
+
+ check_pending_separator (directory);
+
+ directory->contents = g_slist_append (directory->contents,
+ matemenu_tree_item_ref (alias));
+}
+
+static void
+merge_subdir (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ MateMenuTreeDirectory *subdir)
+{
+ menu_verbose ("Merging subdir '%s' in directory '%s'\n",
+ subdir->name, directory->name);
+
+ process_layout_info (tree, subdir);
+
+ check_pending_separator (directory);
+
+ if (subdir->will_inline_header == 0 ||
+ (subdir->will_inline_header != G_MAXUINT16 &&
+ g_slist_length (subdir->contents) <= subdir->will_inline_header))
+ {
+ MateMenuTreeHeader *header;
+
+ header = matemenu_tree_header_new (directory, subdir);
+ directory->contents = g_slist_append (directory->contents, header);
+
+ g_slist_foreach (subdir->contents,
+ (GFunc) matemenu_tree_item_set_parent,
+ directory);
+ directory->contents = g_slist_concat (directory->contents,
+ subdir->contents);
+ subdir->contents = NULL;
+ subdir->will_inline_header = G_MAXUINT16;
+
+ matemenu_tree_item_set_parent (MATEMENU_TREE_ITEM (subdir), NULL);
+ }
+ else
+ {
+ directory->contents = g_slist_append (directory->contents,
+ matemenu_tree_item_ref (subdir));
+ }
+}
+
+static void
+merge_subdir_by_name (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ const char *subdir_name)
+{
+ GSList *tmp;
+
+ menu_verbose ("Attempting to merge subdir '%s' in directory '%s'\n",
+ subdir_name, directory->name);
+
+ tmp = directory->subdirs;
+ while (tmp != NULL)
+ {
+ MateMenuTreeDirectory *subdir = tmp->data;
+ GSList *next = tmp->next;
+
+ /* if it's an alias, then it cannot be affected by
+ * the Merge nodes in the layout */
+ if (MATEMENU_TREE_ITEM (subdir)->type == MATEMENU_TREE_ITEM_ALIAS)
+ continue;
+
+ if (!strcmp (subdir->name, subdir_name))
+ {
+ directory->subdirs = g_slist_delete_link (directory->subdirs, tmp);
+ merge_subdir (tree, directory, subdir);
+ matemenu_tree_item_unref (subdir);
+ }
+
+ tmp = next;
+ }
+}
+
+static void
+merge_entry (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ MateMenuTreeEntry *entry)
+{
+ menu_verbose ("Merging entry '%s' in directory '%s'\n",
+ entry->desktop_file_id, directory->name);
+
+ check_pending_separator (directory);
+ directory->contents = g_slist_append (directory->contents,
+ matemenu_tree_item_ref (entry));
+}
+
+static void
+merge_entry_by_id (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ const char *file_id)
+{
+ GSList *tmp;
+
+ menu_verbose ("Attempting to merge entry '%s' in directory '%s'\n",
+ file_id, directory->name);
+
+ tmp = directory->entries;
+ while (tmp != NULL)
+ {
+ MateMenuTreeEntry *entry = tmp->data;
+ GSList *next = tmp->next;
+
+ /* if it's an alias, then it cannot be affected by
+ * the Merge nodes in the layout */
+ if (MATEMENU_TREE_ITEM (entry)->type == MATEMENU_TREE_ITEM_ALIAS)
+ continue;
+
+ if (!strcmp (entry->desktop_file_id, file_id))
+ {
+ directory->entries = g_slist_delete_link (directory->entries, tmp);
+ merge_entry (tree, directory, entry);
+ matemenu_tree_item_unref (entry);
+ }
+
+ tmp = next;
+ }
+}
+
+static inline gboolean
+find_name_in_list (const char *name,
+ GSList *list)
+{
+ while (list != NULL)
+ {
+ if (!strcmp (name, list->data))
+ return TRUE;
+
+ list = list->next;
+ }
+
+ return FALSE;
+}
+
+static void
+merge_subdirs (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ GSList *except)
+{
+ GSList *subdirs;
+ GSList *tmp;
+
+ menu_verbose ("Merging subdirs in directory '%s'\n", directory->name);
+
+ subdirs = directory->subdirs;
+ directory->subdirs = NULL;
+
+ subdirs = g_slist_sort_with_data (subdirs,
+ (GCompareDataFunc) matemenu_tree_item_compare,
+ GINT_TO_POINTER (MATEMENU_TREE_SORT_NAME));
+
+ tmp = subdirs;
+ while (tmp != NULL)
+ {
+ MateMenuTreeDirectory *subdir = tmp->data;
+
+ if (MATEMENU_TREE_ITEM (subdir)->type == MATEMENU_TREE_ITEM_ALIAS)
+ {
+ merge_alias (tree, directory, MATEMENU_TREE_ALIAS (subdir));
+ matemenu_tree_item_unref (subdir);
+ }
+ else if (!find_name_in_list (subdir->name, except))
+ {
+ merge_subdir (tree, directory, subdir);
+ matemenu_tree_item_unref (subdir);
+ }
+ else
+ {
+ menu_verbose ("Not merging directory '%s' yet\n", subdir->name);
+ directory->subdirs = g_slist_append (directory->subdirs, subdir);
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (subdirs);
+ g_slist_free (except);
+}
+
+static void
+merge_entries (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ GSList *except)
+{
+ GSList *entries;
+ GSList *tmp;
+
+ menu_verbose ("Merging entries in directory '%s'\n", directory->name);
+
+ entries = directory->entries;
+ directory->entries = NULL;
+
+ entries = g_slist_sort_with_data (entries,
+ (GCompareDataFunc) matemenu_tree_item_compare,
+ GINT_TO_POINTER (tree->sort_key));
+
+ tmp = entries;
+ while (tmp != NULL)
+ {
+ MateMenuTreeEntry *entry = tmp->data;
+
+ if (MATEMENU_TREE_ITEM (entry)->type == MATEMENU_TREE_ITEM_ALIAS)
+ {
+ merge_alias (tree, directory, MATEMENU_TREE_ALIAS (entry));
+ matemenu_tree_item_unref (entry);
+ }
+ else if (!find_name_in_list (entry->desktop_file_id, except))
+ {
+ merge_entry (tree, directory, entry);
+ matemenu_tree_item_unref (entry);
+ }
+ else
+ {
+ menu_verbose ("Not merging entry '%s' yet\n", entry->desktop_file_id);
+ directory->entries = g_slist_append (directory->entries, entry);
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (entries);
+ g_slist_free (except);
+}
+
+static void
+merge_subdirs_and_entries (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ GSList *except_subdirs,
+ GSList *except_entries)
+{
+ GSList *items;
+ GSList *tmp;
+
+ menu_verbose ("Merging subdirs and entries together in directory %s\n",
+ directory->name);
+
+ items = g_slist_concat (directory->subdirs, directory->entries);
+
+ directory->subdirs = NULL;
+ directory->entries = NULL;
+
+ items = g_slist_sort_with_data (items,
+ (GCompareDataFunc) matemenu_tree_item_compare,
+ GINT_TO_POINTER (tree->sort_key));
+
+ tmp = items;
+ while (tmp != NULL)
+ {
+ MateMenuTreeItem *item = tmp->data;
+ MateMenuTreeItemType type;
+
+ type = matemenu_tree_item_get_type (item);
+
+ if (type == MATEMENU_TREE_ITEM_ALIAS)
+ {
+ merge_alias (tree, directory, MATEMENU_TREE_ALIAS (item));
+ matemenu_tree_item_unref (item);
+ }
+ else if (type == MATEMENU_TREE_ITEM_DIRECTORY)
+ {
+ if (!find_name_in_list (MATEMENU_TREE_DIRECTORY (item)->name, except_subdirs))
+ {
+ merge_subdir (tree,
+ directory,
+ MATEMENU_TREE_DIRECTORY (item));
+ matemenu_tree_item_unref (item);
+ }
+ else
+ {
+ menu_verbose ("Not merging directory '%s' yet\n",
+ MATEMENU_TREE_DIRECTORY (item)->name);
+ directory->subdirs = g_slist_append (directory->subdirs, item);
+ }
+ }
+ else if (type == MATEMENU_TREE_ITEM_ENTRY)
+ {
+ if (!find_name_in_list (MATEMENU_TREE_ENTRY (item)->desktop_file_id, except_entries))
+ {
+ merge_entry (tree, directory, MATEMENU_TREE_ENTRY (item));
+ matemenu_tree_item_unref (item);
+ }
+ else
+ {
+ menu_verbose ("Not merging entry '%s' yet\n",
+ MATEMENU_TREE_ENTRY (item)->desktop_file_id);
+ directory->entries = g_slist_append (directory->entries, item);
+ }
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (items);
+ g_slist_free (except_subdirs);
+ g_slist_free (except_entries);
+}
+
+static GSList *
+get_subdirs_from_layout_info (GSList *layout_info)
+{
+ GSList *subdirs;
+ GSList *tmp;
+
+ subdirs = NULL;
+
+ tmp = layout_info;
+ while (tmp != NULL)
+ {
+ MenuLayoutNode *node = tmp->data;
+
+ if (menu_layout_node_get_type (node) == MENU_LAYOUT_NODE_MENUNAME)
+ {
+ subdirs = g_slist_append (subdirs,
+ (char *) menu_layout_node_get_content (node));
+ }
+
+ tmp = tmp->next;
+ }
+
+ return subdirs;
+}
+
+static GSList *
+get_entries_from_layout_info (GSList *layout_info)
+{
+ GSList *entries;
+ GSList *tmp;
+
+ entries = NULL;
+
+ tmp = layout_info;
+ while (tmp != NULL)
+ {
+ MenuLayoutNode *node = tmp->data;
+
+ if (menu_layout_node_get_type (node) == MENU_LAYOUT_NODE_FILENAME)
+ {
+ entries = g_slist_append (entries,
+ (char *) menu_layout_node_get_content (node));
+ }
+
+ tmp = tmp->next;
+ }
+
+ return entries;
+}
+
+static void
+process_layout_info (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory)
+{
+ GSList *layout_info;
+
+ menu_verbose ("Processing menu layout hints for %s\n", directory->name);
+
+ g_slist_foreach (directory->contents,
+ (GFunc) matemenu_tree_item_unref_and_unset_parent,
+ NULL);
+ g_slist_free (directory->contents);
+ directory->contents = NULL;
+ directory->layout_pending_separator = FALSE;
+
+ layout_info = get_layout_info (directory, NULL);
+
+ if (layout_info == NULL)
+ {
+ merge_subdirs (tree, directory, NULL);
+ merge_entries (tree, directory, NULL);
+ }
+ else
+ {
+ GSList *tmp;
+
+ tmp = layout_info;
+ while (tmp != NULL)
+ {
+ MenuLayoutNode *node = tmp->data;
+
+ switch (menu_layout_node_get_type (node))
+ {
+ case MENU_LAYOUT_NODE_MENUNAME:
+ merge_subdir_by_name (tree,
+ directory,
+ menu_layout_node_get_content (node));
+ break;
+
+ case MENU_LAYOUT_NODE_FILENAME:
+ merge_entry_by_id (tree,
+ directory,
+ menu_layout_node_get_content (node));
+ break;
+
+ case MENU_LAYOUT_NODE_SEPARATOR:
+ /* Unless explicitly told to show all separators, do not show a
+ * separator at the beginning of a menu. Note that we don't add
+ * the separators now, and instead make it pending. This way, we
+ * won't show two consecutive separators nor will we show a
+ * separator at the end of a menu. */
+ if (tree->flags & MATEMENU_TREE_FLAGS_SHOW_ALL_SEPARATORS)
+ {
+ directory->layout_pending_separator = TRUE;
+ check_pending_separator (directory);
+ }
+ else if (directory->contents)
+ {
+ menu_verbose ("Adding a potential separator in '%s'\n",
+ directory->name);
+
+ directory->layout_pending_separator = TRUE;
+ }
+ else
+ {
+ menu_verbose ("Skipping separator at the beginning of '%s'\n",
+ directory->name);
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_MERGE:
+ switch (menu_layout_node_merge_get_type (node))
+ {
+ case MENU_LAYOUT_MERGE_NONE:
+ break;
+
+ case MENU_LAYOUT_MERGE_MENUS:
+ merge_subdirs (tree,
+ directory,
+ get_subdirs_from_layout_info (tmp->next));
+ break;
+
+ case MENU_LAYOUT_MERGE_FILES:
+ merge_entries (tree,
+ directory,
+ get_entries_from_layout_info (tmp->next));
+ break;
+
+ case MENU_LAYOUT_MERGE_ALL:
+ merge_subdirs_and_entries (tree,
+ directory,
+ get_subdirs_from_layout_info (tmp->next),
+ get_entries_from_layout_info (tmp->next));
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ tmp = tmp->next;
+ }
+ }
+
+ g_slist_foreach (directory->subdirs,
+ (GFunc) matemenu_tree_item_unref,
+ NULL);
+ g_slist_free (directory->subdirs);
+ directory->subdirs = NULL;
+
+ g_slist_foreach (directory->entries,
+ (GFunc) matemenu_tree_item_unref,
+ NULL);
+ g_slist_free (directory->entries);
+ directory->entries = NULL;
+
+ g_slist_foreach (directory->default_layout_info,
+ (GFunc) menu_layout_node_unref,
+ NULL);
+ g_slist_free (directory->default_layout_info);
+ directory->default_layout_info = NULL;
+
+ g_slist_foreach (directory->layout_info,
+ (GFunc) menu_layout_node_unref,
+ NULL);
+ g_slist_free (directory->layout_info);
+ directory->layout_info = NULL;
+}
+
+static void
+handle_entries_changed (MenuLayoutNode *layout,
+ MateMenuTree *tree)
+{
+ if (tree->layout == layout)
+ {
+ matemenu_tree_force_rebuild (tree);
+ matemenu_tree_invoke_monitors (tree);
+ }
+}
+
+static void
+matemenu_tree_build_from_layout (MateMenuTree *tree)
+{
+ DesktopEntrySet *allocated;
+
+ if (tree->root)
+ return;
+
+ matemenu_tree_load_layout (tree);
+ if (!tree->layout)
+ return;
+
+ menu_verbose ("Building menu tree from layout\n");
+
+ allocated = desktop_entry_set_new ();
+
+ /* create the menu structure */
+ tree->root = process_layout (tree,
+ NULL,
+ find_menu_child (tree->layout),
+ allocated);
+ if (tree->root)
+ {
+ matemenu_tree_directory_set_tree (tree->root, tree);
+
+ process_only_unallocated (tree, tree->root, allocated);
+
+ /* process the layout info part that can move/remove items:
+ * inline, show_empty, etc. */
+ preprocess_layout_info (tree, tree->root);
+ /* populate the menu structure that we got with the items, and order it
+ * according to the layout info */
+ process_layout_info (tree, tree->root);
+
+ menu_layout_node_root_add_entries_monitor (tree->layout,
+ (MenuLayoutNodeEntriesChangedFunc) handle_entries_changed,
+ tree);
+ }
+
+ desktop_entry_set_unref (allocated);
+}
+
+static void
+matemenu_tree_force_rebuild (MateMenuTree *tree)
+{
+ if (tree->root)
+ {
+ matemenu_tree_directory_set_tree (tree->root, NULL);
+ matemenu_tree_item_unref (tree->root);
+ tree->root = NULL;
+
+ g_assert (tree->layout != NULL);
+
+ menu_layout_node_root_remove_entries_monitor (tree->layout,
+ (MenuLayoutNodeEntriesChangedFunc) handle_entries_changed,
+ tree);
+ }
+}
diff --git a/libmenu/matemenu-tree.h b/libmenu/matemenu-tree.h
new file mode 100644
index 0000000..d4916a9
--- /dev/null
+++ b/libmenu/matemenu-tree.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2004 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __MATEMENU_TREE_H__
+#define __MATEMENU_TREE_H__
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct MateMenuTree MateMenuTree;
+typedef struct MateMenuTreeItem MateMenuTreeItem;
+typedef struct MateMenuTreeDirectory MateMenuTreeDirectory;
+typedef struct MateMenuTreeEntry MateMenuTreeEntry;
+typedef struct MateMenuTreeSeparator MateMenuTreeSeparator;
+typedef struct MateMenuTreeHeader MateMenuTreeHeader;
+typedef struct MateMenuTreeAlias MateMenuTreeAlias;
+
+typedef void (*MateMenuTreeChangedFunc) (MateMenuTree* tree, gpointer user_data);
+
+typedef enum {
+ MATEMENU_TREE_ITEM_INVALID = 0,
+ MATEMENU_TREE_ITEM_DIRECTORY,
+ MATEMENU_TREE_ITEM_ENTRY,
+ MATEMENU_TREE_ITEM_SEPARATOR,
+ MATEMENU_TREE_ITEM_HEADER,
+ MATEMENU_TREE_ITEM_ALIAS
+} MateMenuTreeItemType;
+
+#define MATEMENU_TREE_ITEM(i) ((MateMenuTreeItem*)(i))
+#define MATEMENU_TREE_DIRECTORY(i) ((MateMenuTreeDirectory*)(i))
+#define MATEMENU_TREE_ENTRY(i) ((MateMenuTreeEntry*)(i))
+#define MATEMENU_TREE_SEPARATOR(i) ((MateMenuTreeSeparator*)(i))
+#define MATEMENU_TREE_HEADER(i) ((MateMenuTreeHeader*)(i))
+#define MATEMENU_TREE_ALIAS(i) ((MateMenuTreeAlias*)(i))
+
+typedef enum {
+ MATEMENU_TREE_FLAGS_NONE = 0,
+ MATEMENU_TREE_FLAGS_INCLUDE_EXCLUDED = 1 << 0,
+ MATEMENU_TREE_FLAGS_SHOW_EMPTY = 1 << 1,
+ MATEMENU_TREE_FLAGS_INCLUDE_NODISPLAY = 1 << 2,
+ MATEMENU_TREE_FLAGS_SHOW_ALL_SEPARATORS = 1 << 3,
+ MATEMENU_TREE_FLAGS_MASK = 0x0f
+} MateMenuTreeFlags;
+
+typedef enum {
+ #define MATEMENU_TREE_SORT_FIRST MATEMENU_TREE_SORT_NAME
+ MATEMENU_TREE_SORT_NAME = 0,
+ MATEMENU_TREE_SORT_DISPLAY_NAME
+ #define MATEMENU_TREE_SORT_LAST MATEMENU_TREE_SORT_DISPLAY_NAME
+} MateMenuTreeSortKey;
+
+MateMenuTree* matemenu_tree_lookup(const char* menu_file, MateMenuTreeFlags flags);
+
+MateMenuTree* matemenu_tree_ref(MateMenuTree* tree);
+void matemenu_tree_unref(MateMenuTree* tree);
+
+void matemenu_tree_set_user_data(MateMenuTree* tree, gpointer user_data, GDestroyNotify dnotify);
+gpointer matemenu_tree_get_user_data(MateMenuTree* tree);
+
+const char* matemenu_tree_get_menu_file(MateMenuTree* tree);
+MateMenuTreeDirectory* matemenu_tree_get_root_directory(MateMenuTree* tree);
+MateMenuTreeDirectory* matemenu_tree_get_directory_from_path(MateMenuTree* tree, const char* path);
+
+MateMenuTreeSortKey matemenu_tree_get_sort_key(MateMenuTree* tree);
+void matemenu_tree_set_sort_key(MateMenuTree* tree, MateMenuTreeSortKey sort_key);
+
+
+
+gpointer matemenu_tree_item_ref(gpointer item);
+void matemenu_tree_item_unref(gpointer item);
+
+void matemenu_tree_item_set_user_data(MateMenuTreeItem* item, gpointer user_data, GDestroyNotify dnotify);
+gpointer matemenu_tree_item_get_user_data(MateMenuTreeItem* item);
+
+MateMenuTreeItemType matemenu_tree_item_get_type(MateMenuTreeItem* item);
+MateMenuTreeDirectory* matemenu_tree_item_get_parent(MateMenuTreeItem* item);
+
+
+GSList* matemenu_tree_directory_get_contents(MateMenuTreeDirectory* directory);
+const char* matemenu_tree_directory_get_name(MateMenuTreeDirectory* directory);
+const char* matemenu_tree_directory_get_comment(MateMenuTreeDirectory* directory);
+const char* matemenu_tree_directory_get_icon(MateMenuTreeDirectory* directory);
+const char* matemenu_tree_directory_get_desktop_file_path(MateMenuTreeDirectory* directory);
+const char* matemenu_tree_directory_get_menu_id(MateMenuTreeDirectory* directory);
+MateMenuTree* matemenu_tree_directory_get_tree(MateMenuTreeDirectory* directory);
+
+gboolean matemenu_tree_directory_get_is_nodisplay(MateMenuTreeDirectory* directory);
+
+char* matemenu_tree_directory_make_path(MateMenuTreeDirectory* directory, MateMenuTreeEntry* entry);
+
+
+const char* matemenu_tree_entry_get_name(MateMenuTreeEntry* entry);
+const char* matemenu_tree_entry_get_generic_name(MateMenuTreeEntry* entry);
+const char* matemenu_tree_entry_get_display_name(MateMenuTreeEntry* entry);
+const char* matemenu_tree_entry_get_comment(MateMenuTreeEntry* entry);
+const char* matemenu_tree_entry_get_icon(MateMenuTreeEntry* entry);
+const char* matemenu_tree_entry_get_exec(MateMenuTreeEntry* entry);
+gboolean matemenu_tree_entry_get_launch_in_terminal(MateMenuTreeEntry* entry);
+const char* matemenu_tree_entry_get_desktop_file_path(MateMenuTreeEntry* entry);
+const char* matemenu_tree_entry_get_desktop_file_id(MateMenuTreeEntry* entry);
+gboolean matemenu_tree_entry_get_is_excluded(MateMenuTreeEntry* entry);
+gboolean matemenu_tree_entry_get_is_nodisplay(MateMenuTreeEntry* entry);
+
+MateMenuTreeDirectory* matemenu_tree_header_get_directory(MateMenuTreeHeader* header);
+
+MateMenuTreeDirectory* matemenu_tree_alias_get_directory(MateMenuTreeAlias* alias);
+MateMenuTreeItem* matemenu_tree_alias_get_item(MateMenuTreeAlias* alias);
+
+void matemenu_tree_add_monitor(MateMenuTree* tree, MateMenuTreeChangedFunc callback, gpointer user_data);
+void matemenu_tree_remove_monitor(MateMenuTree* tree, MateMenuTreeChangedFunc callback, gpointer user_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MATEMENU_TREE_H__ */
diff --git a/libmenu/menu-layout.c b/libmenu/menu-layout.c
new file mode 100644
index 0000000..d447d2d
--- /dev/null
+++ b/libmenu/menu-layout.c
@@ -0,0 +1,2359 @@
+/* Menu layout in-memory data structure (a custom "DOM tree") */
+
+/*
+ * Copyright (C) 2002 - 2004 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include "menu-layout.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "canonicalize.h"
+#include "entry-directories.h"
+#include "menu-util.h"
+
+typedef struct MenuLayoutNodeMenu MenuLayoutNodeMenu;
+typedef struct MenuLayoutNodeRoot MenuLayoutNodeRoot;
+typedef struct MenuLayoutNodeLegacyDir MenuLayoutNodeLegacyDir;
+typedef struct MenuLayoutNodeMergeFile MenuLayoutNodeMergeFile;
+typedef struct MenuLayoutNodeDefaultLayout MenuLayoutNodeDefaultLayout;
+typedef struct MenuLayoutNodeMenuname MenuLayoutNodeMenuname;
+typedef struct MenuLayoutNodeMerge MenuLayoutNodeMerge;
+
+struct MenuLayoutNode
+{
+ /* Node lists are circular, for length-one lists
+ * prev/next point back to the node itself.
+ */
+ MenuLayoutNode *prev;
+ MenuLayoutNode *next;
+ MenuLayoutNode *parent;
+ MenuLayoutNode *children;
+
+ char *content;
+
+ guint refcount : 20;
+ guint type : 7;
+};
+
+struct MenuLayoutNodeRoot
+{
+ MenuLayoutNode node;
+
+ char *basedir;
+ char *name;
+
+ GSList *monitors;
+};
+
+struct MenuLayoutNodeMenu
+{
+ MenuLayoutNode node;
+
+ MenuLayoutNode *name_node; /* cache of the <Name> node */
+
+ EntryDirectoryList *app_dirs;
+ EntryDirectoryList *dir_dirs;
+};
+
+struct MenuLayoutNodeLegacyDir
+{
+ MenuLayoutNode node;
+
+ char *prefix;
+};
+
+struct MenuLayoutNodeMergeFile
+{
+ MenuLayoutNode node;
+
+ MenuMergeFileType type;
+};
+
+struct MenuLayoutNodeDefaultLayout
+{
+ MenuLayoutNode node;
+
+ MenuLayoutValues layout_values;
+};
+
+struct MenuLayoutNodeMenuname
+{
+ MenuLayoutNode node;
+
+ MenuLayoutValues layout_values;
+};
+
+struct MenuLayoutNodeMerge
+{
+ MenuLayoutNode node;
+
+ MenuLayoutMergeType merge_type;
+};
+
+typedef struct
+{
+ MenuLayoutNodeEntriesChangedFunc callback;
+ gpointer user_data;
+} MenuLayoutNodeEntriesMonitor;
+
+
+static inline MenuLayoutNode *
+node_next (MenuLayoutNode *node)
+{
+ /* root nodes (no parent) never have siblings */
+ if (node->parent == NULL)
+ return NULL;
+
+ /* circular list */
+ if (node->next == node->parent->children)
+ return NULL;
+
+ return node->next;
+}
+
+static void
+handle_entry_directory_changed (EntryDirectory *dir,
+ MenuLayoutNode *node)
+{
+ MenuLayoutNodeRoot *nr;
+ GSList *tmp;
+
+ g_assert (node->type == MENU_LAYOUT_NODE_MENU);
+
+ nr = (MenuLayoutNodeRoot *) menu_layout_node_get_root (node);
+
+ tmp = nr->monitors;
+ while (tmp != NULL)
+ {
+ MenuLayoutNodeEntriesMonitor *monitor = tmp->data;
+ GSList *next = tmp->next;
+
+ monitor->callback ((MenuLayoutNode *) nr, monitor->user_data);
+
+ tmp = next;
+ }
+}
+
+static void
+remove_entry_directory_list (MenuLayoutNodeMenu *nm,
+ EntryDirectoryList **dirs)
+{
+ if (*dirs)
+ {
+ entry_directory_list_remove_monitors (*dirs,
+ (EntryDirectoryChangedFunc) handle_entry_directory_changed,
+ nm);
+ entry_directory_list_unref (*dirs);
+ *dirs = NULL;
+ }
+}
+
+MenuLayoutNode *
+menu_layout_node_ref (MenuLayoutNode *node)
+{
+ g_return_val_if_fail (node != NULL, NULL);
+
+ node->refcount += 1;
+
+ return node;
+}
+
+void
+menu_layout_node_unref (MenuLayoutNode *node)
+{
+ g_return_if_fail (node != NULL);
+ g_return_if_fail (node->refcount > 0);
+
+ node->refcount -= 1;
+ if (node->refcount == 0)
+ {
+ MenuLayoutNode *iter;
+
+ iter = node->children;
+ while (iter != NULL)
+ {
+ MenuLayoutNode *next = node_next (iter);
+
+ menu_layout_node_unref (iter);
+
+ iter = next;
+ }
+
+ if (node->type == MENU_LAYOUT_NODE_MENU)
+ {
+ MenuLayoutNodeMenu *nm = (MenuLayoutNodeMenu *) node;
+
+ if (nm->name_node)
+ menu_layout_node_unref (nm->name_node);
+
+ remove_entry_directory_list (nm, &nm->app_dirs);
+ remove_entry_directory_list (nm, &nm->dir_dirs);
+ }
+ else if (node->type == MENU_LAYOUT_NODE_LEGACY_DIR)
+ {
+ MenuLayoutNodeLegacyDir *legacy = (MenuLayoutNodeLegacyDir *) node;
+
+ g_free (legacy->prefix);
+ }
+ else if (node->type == MENU_LAYOUT_NODE_ROOT)
+ {
+ MenuLayoutNodeRoot *nr = (MenuLayoutNodeRoot*) node;
+
+ g_slist_foreach (nr->monitors, (GFunc) g_free, NULL);
+ g_slist_free (nr->monitors);
+
+ g_free (nr->basedir);
+ g_free (nr->name);
+ }
+
+ g_free (node->content);
+ g_free (node);
+ }
+}
+
+MenuLayoutNode *
+menu_layout_node_new (MenuLayoutNodeType type)
+{
+ MenuLayoutNode *node;
+
+ switch (type)
+ {
+ case MENU_LAYOUT_NODE_MENU:
+ node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeMenu, 1);
+ break;
+
+ case MENU_LAYOUT_NODE_LEGACY_DIR:
+ node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeLegacyDir, 1);
+ break;
+
+ case MENU_LAYOUT_NODE_ROOT:
+ node = (MenuLayoutNode*) g_new0 (MenuLayoutNodeRoot, 1);
+ break;
+
+ case MENU_LAYOUT_NODE_MERGE_FILE:
+ node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeMergeFile, 1);
+ break;
+
+ case MENU_LAYOUT_NODE_DEFAULT_LAYOUT:
+ node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeDefaultLayout, 1);
+ break;
+
+ case MENU_LAYOUT_NODE_MENUNAME:
+ node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeMenuname, 1);
+ break;
+
+ case MENU_LAYOUT_NODE_MERGE:
+ node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeMerge, 1);
+ break;
+
+ default:
+ node = g_new0 (MenuLayoutNode, 1);
+ break;
+ }
+
+ node->type = type;
+
+ node->refcount = 1;
+
+ /* we're in a list of one node */
+ node->next = node;
+ node->prev = node;
+
+ return node;
+}
+
+MenuLayoutNode *
+menu_layout_node_get_next (MenuLayoutNode *node)
+{
+ return node_next (node);
+}
+
+MenuLayoutNode *
+menu_layout_node_get_parent (MenuLayoutNode *node)
+{
+ return node->parent;
+}
+
+MenuLayoutNode *
+menu_layout_node_get_children (MenuLayoutNode *node)
+{
+ return node->children;
+}
+
+MenuLayoutNode *
+menu_layout_node_get_root (MenuLayoutNode *node)
+{
+ MenuLayoutNode *parent;
+
+ parent = node;
+ while (parent->parent != NULL)
+ parent = parent->parent;
+
+ g_assert (parent->type == MENU_LAYOUT_NODE_ROOT);
+
+ return parent;
+}
+
+char *
+menu_layout_node_get_content_as_path (MenuLayoutNode *node)
+{
+ if (node->content == NULL)
+ {
+ menu_verbose (" (node has no content to get as a path)\n");
+ return NULL;
+ }
+
+ if (g_path_is_absolute (node->content))
+ {
+ return g_strdup (node->content);
+ }
+ else
+ {
+ MenuLayoutNodeRoot *root;
+
+ root = (MenuLayoutNodeRoot *) menu_layout_node_get_root (node);
+
+ if (root->basedir == NULL)
+ {
+ menu_verbose ("No basedir available, using \"%s\" as-is\n",
+ node->content);
+ return g_strdup (node->content);
+ }
+ else
+ {
+ menu_verbose ("Using basedir \"%s\" filename \"%s\"\n",
+ root->basedir, node->content);
+ return g_build_filename (root->basedir, node->content, NULL);
+ }
+ }
+}
+
+#define RETURN_IF_NO_PARENT(node) G_STMT_START { \
+ if ((node)->parent == NULL) \
+ { \
+ g_warning ("To add siblings to a menu node, " \
+ "it must not be the root node, " \
+ "and must be linked in below some root node\n" \
+ "node parent = %p and type = %d", \
+ (node)->parent, (node)->type); \
+ return; \
+ } \
+ } G_STMT_END
+
+#define RETURN_IF_HAS_ENTRY_DIRS(node) G_STMT_START { \
+ if ((node)->type == MENU_LAYOUT_NODE_MENU && \
+ (((MenuLayoutNodeMenu*)(node))->app_dirs != NULL || \
+ ((MenuLayoutNodeMenu*)(node))->dir_dirs != NULL)) \
+ { \
+ g_warning ("node acquired ->app_dirs or ->dir_dirs " \
+ "while not rooted in a tree\n"); \
+ return; \
+ } \
+ } G_STMT_END \
+
+void
+menu_layout_node_insert_before (MenuLayoutNode *node,
+ MenuLayoutNode *new_sibling)
+{
+ g_return_if_fail (new_sibling != NULL);
+ g_return_if_fail (new_sibling->parent == NULL);
+
+ RETURN_IF_NO_PARENT (node);
+ RETURN_IF_HAS_ENTRY_DIRS (new_sibling);
+
+ new_sibling->next = node;
+ new_sibling->prev = node->prev;
+
+ node->prev = new_sibling;
+ new_sibling->prev->next = new_sibling;
+
+ new_sibling->parent = node->parent;
+
+ if (node == node->parent->children)
+ node->parent->children = new_sibling;
+
+ menu_layout_node_ref (new_sibling);
+}
+
+void
+menu_layout_node_insert_after (MenuLayoutNode *node,
+ MenuLayoutNode *new_sibling)
+{
+ g_return_if_fail (new_sibling != NULL);
+ g_return_if_fail (new_sibling->parent == NULL);
+
+ RETURN_IF_NO_PARENT (node);
+ RETURN_IF_HAS_ENTRY_DIRS (new_sibling);
+
+ new_sibling->prev = node;
+ new_sibling->next = node->next;
+
+ node->next = new_sibling;
+ new_sibling->next->prev = new_sibling;
+
+ new_sibling->parent = node->parent;
+
+ menu_layout_node_ref (new_sibling);
+}
+
+void
+menu_layout_node_prepend_child (MenuLayoutNode *parent,
+ MenuLayoutNode *new_child)
+{
+ RETURN_IF_HAS_ENTRY_DIRS (new_child);
+
+ if (parent->children)
+ {
+ menu_layout_node_insert_before (parent->children, new_child);
+ }
+ else
+ {
+ parent->children = menu_layout_node_ref (new_child);
+ new_child->parent = parent;
+ }
+}
+
+void
+menu_layout_node_append_child (MenuLayoutNode *parent,
+ MenuLayoutNode *new_child)
+{
+ RETURN_IF_HAS_ENTRY_DIRS (new_child);
+
+ if (parent->children)
+ {
+ menu_layout_node_insert_after (parent->children->prev, new_child);
+ }
+ else
+ {
+ parent->children = menu_layout_node_ref (new_child);
+ new_child->parent = parent;
+ }
+}
+
+void
+menu_layout_node_unlink (MenuLayoutNode *node)
+{
+ g_return_if_fail (node != NULL);
+ g_return_if_fail (node->parent != NULL);
+
+ menu_layout_node_steal (node);
+ menu_layout_node_unref (node);
+}
+
+static void
+recursive_clean_entry_directory_lists (MenuLayoutNode *node,
+ gboolean apps)
+{
+ EntryDirectoryList **dirs;
+ MenuLayoutNodeMenu *nm;
+ MenuLayoutNode *iter;
+
+ if (node->type != MENU_LAYOUT_NODE_MENU)
+ return;
+
+ nm = (MenuLayoutNodeMenu *) node;
+
+ dirs = apps ? &nm->app_dirs : &nm->dir_dirs;
+
+ if (*dirs == NULL || entry_directory_list_get_length (*dirs) == 0)
+ return; /* child menus continue to have valid lists */
+
+ remove_entry_directory_list (nm, dirs);
+
+ iter = node->children;
+ while (iter != NULL)
+ {
+ if (iter->type == MENU_LAYOUT_NODE_MENU)
+ recursive_clean_entry_directory_lists (iter, apps);
+
+ iter = node_next (iter);
+ }
+}
+
+void
+menu_layout_node_steal (MenuLayoutNode *node)
+{
+ g_return_if_fail (node != NULL);
+ g_return_if_fail (node->parent != NULL);
+
+ switch (node->type)
+ {
+ case MENU_LAYOUT_NODE_NAME:
+ {
+ MenuLayoutNodeMenu *nm = (MenuLayoutNodeMenu *) node->parent;
+
+ if (nm->name_node == node)
+ {
+ menu_layout_node_unref (nm->name_node);
+ nm->name_node = NULL;
+ }
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_APP_DIR:
+ recursive_clean_entry_directory_lists (node->parent, TRUE);
+ break;
+
+ case MENU_LAYOUT_NODE_DIRECTORY_DIR:
+ recursive_clean_entry_directory_lists (node->parent, FALSE);
+ break;
+
+ default:
+ break;
+ }
+
+ if (node->parent && node->parent->children == node)
+ {
+ if (node->next != node)
+ node->parent->children = node->next;
+ else
+ node->parent->children = NULL;
+ }
+
+ /* these are no-ops for length-one node lists */
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+
+ node->parent = NULL;
+
+ /* point to ourselves, now we're length one */
+ node->next = node;
+ node->prev = node;
+}
+
+MenuLayoutNodeType
+menu_layout_node_get_type (MenuLayoutNode *node)
+{
+ return node->type;
+}
+
+const char *
+menu_layout_node_get_content (MenuLayoutNode *node)
+{
+ return node->content;
+}
+
+void
+menu_layout_node_set_content (MenuLayoutNode *node,
+ const char *content)
+{
+ if (node->content == content)
+ return;
+
+ g_free (node->content);
+ node->content = g_strdup (content);
+}
+
+const char *
+menu_layout_node_root_get_name (MenuLayoutNode *node)
+{
+ MenuLayoutNodeRoot *nr;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_ROOT, NULL);
+
+ nr = (MenuLayoutNodeRoot*) node;
+
+ return nr->name;
+}
+
+const char *
+menu_layout_node_root_get_basedir (MenuLayoutNode *node)
+{
+ MenuLayoutNodeRoot *nr;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_ROOT, NULL);
+
+ nr = (MenuLayoutNodeRoot*) node;
+
+ return nr->basedir;
+}
+
+const char *
+menu_layout_node_menu_get_name (MenuLayoutNode *node)
+{
+ MenuLayoutNodeMenu *nm;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MENU, NULL);
+
+ nm = (MenuLayoutNodeMenu*) node;
+
+ if (nm->name_node == NULL)
+ {
+ MenuLayoutNode *iter;
+
+ iter = node->children;
+ while (iter != NULL)
+ {
+ if (iter->type == MENU_LAYOUT_NODE_NAME)
+ {
+ nm->name_node = menu_layout_node_ref (iter);
+ break;
+ }
+
+ iter = node_next (iter);
+ }
+ }
+
+ if (nm->name_node == NULL)
+ return NULL;
+
+ return menu_layout_node_get_content (nm->name_node);
+}
+
+static void
+ensure_dir_lists (MenuLayoutNodeMenu *nm)
+{
+ MenuLayoutNode *node;
+ MenuLayoutNode *iter;
+ EntryDirectoryList *app_dirs;
+ EntryDirectoryList *dir_dirs;
+
+ node = (MenuLayoutNode *) nm;
+
+ if (nm->app_dirs && nm->dir_dirs)
+ return;
+
+ app_dirs = NULL;
+ dir_dirs = NULL;
+
+ if (nm->app_dirs == NULL)
+ {
+ app_dirs = entry_directory_list_new ();
+
+ if (node->parent && node->parent->type == MENU_LAYOUT_NODE_MENU)
+ {
+ EntryDirectoryList *dirs;
+
+ if ((dirs = menu_layout_node_menu_get_app_dirs (node->parent)))
+ entry_directory_list_append_list (app_dirs, dirs);
+ }
+ }
+
+ if (nm->dir_dirs == NULL)
+ {
+ dir_dirs = entry_directory_list_new ();
+
+ if (node->parent && node->parent->type == MENU_LAYOUT_NODE_MENU)
+ {
+ EntryDirectoryList *dirs;
+
+ if ((dirs = menu_layout_node_menu_get_directory_dirs (node->parent)))
+ entry_directory_list_append_list (dir_dirs, dirs);
+ }
+ }
+
+ iter = node->children;
+ while (iter != NULL)
+ {
+ EntryDirectory *ed;
+
+ if (app_dirs != NULL && iter->type == MENU_LAYOUT_NODE_APP_DIR)
+ {
+ char *path;
+
+ path = menu_layout_node_get_content_as_path (iter);
+
+ ed = entry_directory_new (DESKTOP_ENTRY_DESKTOP, path);
+ if (ed != NULL)
+ {
+ entry_directory_list_prepend (app_dirs, ed);
+ entry_directory_unref (ed);
+ }
+
+ g_free (path);
+ }
+
+ if (dir_dirs != NULL && iter->type == MENU_LAYOUT_NODE_DIRECTORY_DIR)
+ {
+ char *path;
+
+ path = menu_layout_node_get_content_as_path (iter);
+
+ ed = entry_directory_new (DESKTOP_ENTRY_DIRECTORY, path);
+ if (ed != NULL)
+ {
+ entry_directory_list_prepend (dir_dirs, ed);
+ entry_directory_unref (ed);
+ }
+
+ g_free (path);
+ }
+
+ if (iter->type == MENU_LAYOUT_NODE_LEGACY_DIR)
+ {
+ MenuLayoutNodeLegacyDir *legacy = (MenuLayoutNodeLegacyDir *) iter;
+ char *path;
+
+ path = menu_layout_node_get_content_as_path (iter);
+
+ if (app_dirs != NULL) /* we're loading app dirs */
+ {
+ ed = entry_directory_new_legacy (DESKTOP_ENTRY_DESKTOP,
+ path,
+ legacy->prefix);
+ if (ed != NULL)
+ {
+ entry_directory_list_prepend (app_dirs, ed);
+ entry_directory_unref (ed);
+ }
+ }
+
+ if (dir_dirs != NULL) /* we're loading dir dirs */
+ {
+ ed = entry_directory_new_legacy (DESKTOP_ENTRY_DIRECTORY,
+ path,
+ legacy->prefix);
+ if (ed != NULL)
+ {
+ entry_directory_list_prepend (dir_dirs, ed);
+ entry_directory_unref (ed);
+ }
+ }
+
+ g_free (path);
+ }
+
+ iter = node_next (iter);
+ }
+
+ if (app_dirs)
+ {
+ g_assert (nm->app_dirs == NULL);
+
+ nm->app_dirs = app_dirs;
+ entry_directory_list_add_monitors (nm->app_dirs,
+ (EntryDirectoryChangedFunc) handle_entry_directory_changed,
+ nm);
+ }
+
+ if (dir_dirs)
+ {
+ g_assert (nm->dir_dirs == NULL);
+
+ nm->dir_dirs = dir_dirs;
+ entry_directory_list_add_monitors (nm->dir_dirs,
+ (EntryDirectoryChangedFunc) handle_entry_directory_changed,
+ nm);
+ }
+}
+
+EntryDirectoryList *
+menu_layout_node_menu_get_app_dirs (MenuLayoutNode *node)
+{
+ MenuLayoutNodeMenu *nm;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MENU, NULL);
+
+ nm = (MenuLayoutNodeMenu *) node;
+
+ ensure_dir_lists (nm);
+
+ return nm->app_dirs;
+}
+
+EntryDirectoryList *
+menu_layout_node_menu_get_directory_dirs (MenuLayoutNode *node)
+{
+ MenuLayoutNodeMenu *nm;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MENU, NULL);
+
+ nm = (MenuLayoutNodeMenu *) node;
+
+ ensure_dir_lists (nm);
+
+ return nm->dir_dirs;
+}
+
+const char *
+menu_layout_node_move_get_old (MenuLayoutNode *node)
+{
+ MenuLayoutNode *iter;
+
+ iter = node->children;
+ while (iter != NULL)
+ {
+ if (iter->type == MENU_LAYOUT_NODE_OLD)
+ return iter->content;
+
+ iter = node_next (iter);
+ }
+
+ return NULL;
+}
+
+const char *
+menu_layout_node_move_get_new (MenuLayoutNode *node)
+{
+ MenuLayoutNode *iter;
+
+ iter = node->children;
+ while (iter != NULL)
+ {
+ if (iter->type == MENU_LAYOUT_NODE_NEW)
+ return iter->content;
+
+ iter = node_next (iter);
+ }
+
+ return NULL;
+}
+
+const char *
+menu_layout_node_legacy_dir_get_prefix (MenuLayoutNode *node)
+{
+ MenuLayoutNodeLegacyDir *legacy;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_LEGACY_DIR, NULL);
+
+ legacy = (MenuLayoutNodeLegacyDir *) node;
+
+ return legacy->prefix;
+}
+
+void
+menu_layout_node_legacy_dir_set_prefix (MenuLayoutNode *node,
+ const char *prefix)
+{
+ MenuLayoutNodeLegacyDir *legacy;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_LEGACY_DIR);
+
+ legacy = (MenuLayoutNodeLegacyDir *) node;
+
+ g_free (legacy->prefix);
+ legacy->prefix = g_strdup (prefix);
+}
+
+MenuMergeFileType
+menu_layout_node_merge_file_get_type (MenuLayoutNode *node)
+{
+ MenuLayoutNodeMergeFile *merge_file;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MERGE_FILE, FALSE);
+
+ merge_file = (MenuLayoutNodeMergeFile *) node;
+
+ return merge_file->type;
+}
+
+void
+menu_layout_node_merge_file_set_type (MenuLayoutNode *node,
+ MenuMergeFileType type)
+{
+ MenuLayoutNodeMergeFile *merge_file;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_MERGE_FILE);
+
+ merge_file = (MenuLayoutNodeMergeFile *) node;
+
+ merge_file->type = type;
+}
+
+MenuLayoutMergeType
+menu_layout_node_merge_get_type (MenuLayoutNode *node)
+{
+ MenuLayoutNodeMerge *merge;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MERGE, 0);
+
+ merge = (MenuLayoutNodeMerge *) node;
+
+ return merge->merge_type;
+}
+
+static void
+menu_layout_node_merge_set_type (MenuLayoutNode *node,
+ const char *merge_type)
+{
+ MenuLayoutNodeMerge *merge;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_MERGE);
+
+ merge = (MenuLayoutNodeMerge *) node;
+
+ merge->merge_type = MENU_LAYOUT_MERGE_NONE;
+
+ if (strcmp (merge_type, "menus") == 0)
+ {
+ merge->merge_type = MENU_LAYOUT_MERGE_MENUS;
+ }
+ else if (strcmp (merge_type, "files") == 0)
+ {
+ merge->merge_type = MENU_LAYOUT_MERGE_FILES;
+ }
+ else if (strcmp (merge_type, "all") == 0)
+ {
+ merge->merge_type = MENU_LAYOUT_MERGE_ALL;
+ }
+}
+
+void
+menu_layout_node_default_layout_get_values (MenuLayoutNode *node,
+ MenuLayoutValues *values)
+{
+ MenuLayoutNodeDefaultLayout *default_layout;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_DEFAULT_LAYOUT);
+ g_return_if_fail (values != NULL);
+
+ default_layout = (MenuLayoutNodeDefaultLayout *) node;
+
+ *values = default_layout->layout_values;
+}
+
+void
+menu_layout_node_menuname_get_values (MenuLayoutNode *node,
+ MenuLayoutValues *values)
+{
+ MenuLayoutNodeMenuname *menuname;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_MENUNAME);
+ g_return_if_fail (values != NULL);
+
+ menuname = (MenuLayoutNodeMenuname *) node;
+
+ *values = menuname->layout_values;
+}
+
+static void
+menu_layout_values_set (MenuLayoutValues *values,
+ const char *show_empty,
+ const char *inline_menus,
+ const char *inline_limit,
+ const char *inline_header,
+ const char *inline_alias)
+{
+ values->mask = MENU_LAYOUT_VALUES_NONE;
+ values->show_empty = FALSE;
+ values->inline_menus = FALSE;
+ values->inline_limit = 4;
+ values->inline_header = FALSE;
+ values->inline_alias = FALSE;
+
+ if (show_empty != NULL)
+ {
+ values->show_empty = strcmp (show_empty, "true") == 0;
+ values->mask |= MENU_LAYOUT_VALUES_SHOW_EMPTY;
+ }
+
+ if (inline_menus != NULL)
+ {
+ values->inline_menus = strcmp (inline_menus, "true") == 0;
+ values->mask |= MENU_LAYOUT_VALUES_INLINE_MENUS;
+ }
+
+ if (inline_limit != NULL)
+ {
+ char *end;
+ int limit;
+
+ limit = strtol (inline_limit, &end, 10);
+ if (*end == '\0')
+ {
+ values->inline_limit = limit;
+ values->mask |= MENU_LAYOUT_VALUES_INLINE_LIMIT;
+ }
+ }
+
+ if (inline_header != NULL)
+ {
+ values->inline_header = strcmp (inline_header, "true") == 0;
+ values->mask |= MENU_LAYOUT_VALUES_INLINE_HEADER;
+ }
+
+ if (inline_alias != NULL)
+ {
+ values->inline_alias = strcmp (inline_alias, "true") == 0;
+ values->mask |= MENU_LAYOUT_VALUES_INLINE_ALIAS;
+ }
+}
+
+static void
+menu_layout_node_default_layout_set_values (MenuLayoutNode *node,
+ const char *show_empty,
+ const char *inline_menus,
+ const char *inline_limit,
+ const char *inline_header,
+ const char *inline_alias)
+{
+ MenuLayoutNodeDefaultLayout *default_layout;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_DEFAULT_LAYOUT);
+
+ default_layout = (MenuLayoutNodeDefaultLayout *) node;
+
+ menu_layout_values_set (&default_layout->layout_values,
+ show_empty,
+ inline_menus,
+ inline_limit,
+ inline_header,
+ inline_alias);
+}
+
+static void
+menu_layout_node_menuname_set_values (MenuLayoutNode *node,
+ const char *show_empty,
+ const char *inline_menus,
+ const char *inline_limit,
+ const char *inline_header,
+ const char *inline_alias)
+{
+ MenuLayoutNodeMenuname *menuname;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_MENUNAME);
+
+ menuname = (MenuLayoutNodeMenuname *) node;
+
+ menu_layout_values_set (&menuname->layout_values,
+ show_empty,
+ inline_menus,
+ inline_limit,
+ inline_header,
+ inline_alias);
+}
+
+void
+menu_layout_node_root_add_entries_monitor (MenuLayoutNode *node,
+ MenuLayoutNodeEntriesChangedFunc callback,
+ gpointer user_data)
+{
+ MenuLayoutNodeEntriesMonitor *monitor;
+ MenuLayoutNodeRoot *nr;
+ GSList *tmp;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_ROOT);
+
+ nr = (MenuLayoutNodeRoot *) node;
+
+ tmp = nr->monitors;
+ while (tmp != NULL)
+ {
+ monitor = tmp->data;
+
+ if (monitor->callback == callback &&
+ monitor->user_data == user_data)
+ break;
+
+ tmp = tmp->next;
+ }
+
+ if (tmp == NULL)
+ {
+ monitor = g_new0 (MenuLayoutNodeEntriesMonitor, 1);
+ monitor->callback = callback;
+ monitor->user_data = user_data;
+
+ nr->monitors = g_slist_append (nr->monitors, monitor);
+ }
+}
+
+void
+menu_layout_node_root_remove_entries_monitor (MenuLayoutNode *node,
+ MenuLayoutNodeEntriesChangedFunc callback,
+ gpointer user_data)
+{
+ MenuLayoutNodeRoot *nr;
+ GSList *tmp;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_ROOT);
+
+ nr = (MenuLayoutNodeRoot *) node;
+
+ tmp = nr->monitors;
+ while (tmp != NULL)
+ {
+ MenuLayoutNodeEntriesMonitor *monitor = tmp->data;
+ GSList *next = tmp->next;
+
+ if (monitor->callback == callback &&
+ monitor->user_data == user_data)
+ {
+ nr->monitors = g_slist_delete_link (nr->monitors, tmp);
+ g_free (monitor);
+ }
+
+ tmp = next;
+ }
+}
+
+
+/*
+ * Menu file parsing
+ */
+
+typedef struct
+{
+ MenuLayoutNode *root;
+ MenuLayoutNode *stack_top;
+} MenuParser;
+
+static void set_error (GError **err,
+ GMarkupParseContext *context,
+ int error_domain,
+ int error_code,
+ const char *format,
+ ...) G_GNUC_PRINTF (5, 6);
+
+static void add_context_to_error (GError **err,
+ GMarkupParseContext *context);
+
+static void start_element_handler (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ gpointer user_data,
+ GError **error);
+static void end_element_handler (GMarkupParseContext *context,
+ const char *element_name,
+ gpointer user_data,
+ GError **error);
+static void text_handler (GMarkupParseContext *context,
+ const char *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error);
+static void passthrough_handler (GMarkupParseContext *context,
+ const char *passthrough_text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error);
+
+
+static GMarkupParser menu_funcs = {
+ start_element_handler,
+ end_element_handler,
+ text_handler,
+ passthrough_handler,
+ NULL
+};
+
+static void
+set_error (GError **err,
+ GMarkupParseContext *context,
+ int error_domain,
+ int error_code,
+ const char *format,
+ ...)
+{
+ int line, ch;
+ va_list args;
+ char *str;
+
+ g_markup_parse_context_get_position (context, &line, &ch);
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ g_set_error (err, error_domain, error_code,
+ "Line %d character %d: %s",
+ line, ch, str);
+
+ g_free (str);
+}
+
+static void
+add_context_to_error (GError **err,
+ GMarkupParseContext *context)
+{
+ int line, ch;
+ char *str;
+
+ if (err == NULL || *err == NULL)
+ return;
+
+ g_markup_parse_context_get_position (context, &line, &ch);
+
+ str = g_strdup_printf ("Line %d character %d: %s",
+ line, ch, (*err)->message);
+ g_free ((*err)->message);
+ (*err)->message = str;
+}
+
+#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
+
+typedef struct
+{
+ const char *name;
+ const char **retloc;
+} LocateAttr;
+
+static gboolean
+locate_attributes (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error,
+ const char *first_attribute_name,
+ const char **first_attribute_retloc,
+ ...)
+{
+#define MAX_ATTRS 24
+ LocateAttr attrs[MAX_ATTRS];
+ int n_attrs;
+ va_list args;
+ const char *name;
+ const char **retloc;
+ gboolean retval;
+ int i;
+
+ g_return_val_if_fail (first_attribute_name != NULL, FALSE);
+ g_return_val_if_fail (first_attribute_retloc != NULL, FALSE);
+
+ retval = TRUE;
+
+ n_attrs = 1;
+ attrs[0].name = first_attribute_name;
+ attrs[0].retloc = first_attribute_retloc;
+ *first_attribute_retloc = NULL;
+
+ va_start (args, first_attribute_retloc);
+
+ name = va_arg (args, const char *);
+ retloc = va_arg (args, const char **);
+
+ while (name != NULL)
+ {
+ g_return_val_if_fail (retloc != NULL, FALSE);
+
+ g_assert (n_attrs < MAX_ATTRS);
+
+ attrs[n_attrs].name = name;
+ attrs[n_attrs].retloc = retloc;
+ n_attrs += 1;
+ *retloc = NULL;
+
+ name = va_arg (args, const char *);
+ retloc = va_arg (args, const char **);
+ }
+
+ va_end (args);
+
+ i = 0;
+ while (attribute_names[i])
+ {
+ int j;
+
+ j = 0;
+ while (j < n_attrs)
+ {
+ if (strcmp (attrs[j].name, attribute_names[i]) == 0)
+ {
+ retloc = attrs[j].retloc;
+
+ if (*retloc != NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "Attribute \"%s\" repeated twice on the same <%s> element",
+ attrs[j].name, element_name);
+ retval = FALSE;
+ goto out;
+ }
+
+ *retloc = attribute_values[i];
+ break;
+ }
+
+ ++j;
+ }
+
+ if (j == n_attrs)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "Attribute \"%s\" is invalid on <%s> element in this context",
+ attribute_names[i], element_name);
+ retval = FALSE;
+ goto out;
+ }
+
+ ++i;
+ }
+
+ out:
+ return retval;
+
+#undef MAX_ATTRS
+}
+
+static gboolean
+check_no_attributes (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (attribute_names[0] != NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "Attribute \"%s\" is invalid on <%s> element in this context",
+ attribute_names[0], element_name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int
+has_child_of_type (MenuLayoutNode *node,
+ MenuLayoutNodeType type)
+{
+ MenuLayoutNode *iter;
+
+ iter = node->children;
+ while (iter)
+ {
+ if (iter->type == type)
+ return TRUE;
+
+ iter = node_next (iter);
+ }
+
+ return FALSE;
+}
+
+static void
+push_node (MenuParser *parser,
+ MenuLayoutNodeType type)
+{
+ MenuLayoutNode *node;
+
+ node = menu_layout_node_new (type);
+ menu_layout_node_append_child (parser->stack_top, node);
+ menu_layout_node_unref (node);
+
+ parser->stack_top = node;
+}
+
+static void
+start_menu_element (MenuParser *parser,
+ GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ if (!(parser->stack_top->type == MENU_LAYOUT_NODE_ROOT ||
+ parser->stack_top->type == MENU_LAYOUT_NODE_MENU))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "<Menu> element can only appear below other <Menu> elements or at toplevel\n");
+ }
+ else
+ {
+ push_node (parser, MENU_LAYOUT_NODE_MENU);
+ }
+}
+
+static void
+start_menu_child_element (MenuParser *parser,
+ GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (ELEMENT_IS ("LegacyDir"))
+ {
+ const char *prefix;
+
+ push_node (parser, MENU_LAYOUT_NODE_LEGACY_DIR);
+
+ if (!locate_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error,
+ "prefix", &prefix,
+ NULL))
+ return;
+
+ menu_layout_node_legacy_dir_set_prefix (parser->stack_top, prefix);
+ }
+ else if (ELEMENT_IS ("MergeFile"))
+ {
+ const char *type;
+
+ push_node (parser, MENU_LAYOUT_NODE_MERGE_FILE);
+
+ if (!locate_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error,
+ "type", &type,
+ NULL))
+ return;
+
+ if (type != NULL && strcmp (type, "parent") == 0)
+ {
+ menu_layout_node_merge_file_set_type (parser->stack_top,
+ MENU_MERGE_FILE_TYPE_PARENT);
+ }
+ }
+ else if (ELEMENT_IS ("DefaultLayout"))
+ {
+ const char *show_empty;
+ const char *inline_menus;
+ const char *inline_limit;
+ const char *inline_header;
+ const char *inline_alias;
+
+ push_node (parser, MENU_LAYOUT_NODE_DEFAULT_LAYOUT);
+
+ locate_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error,
+ "show_empty", &show_empty,
+ "inline", &inline_menus,
+ "inline_limit", &inline_limit,
+ "inline_header", &inline_header,
+ "inline_alias", &inline_alias,
+ NULL);
+
+ menu_layout_node_default_layout_set_values (parser->stack_top,
+ show_empty,
+ inline_menus,
+ inline_limit,
+ inline_header,
+ inline_alias);
+ }
+ else
+ {
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ if (ELEMENT_IS ("AppDir"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_APP_DIR);
+ }
+ else if (ELEMENT_IS ("DefaultAppDirs"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_DEFAULT_APP_DIRS);
+ }
+ else if (ELEMENT_IS ("DirectoryDir"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_DIRECTORY_DIR);
+ }
+ else if (ELEMENT_IS ("DefaultDirectoryDirs"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS);
+ }
+ else if (ELEMENT_IS ("DefaultMergeDirs"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS);
+ }
+ else if (ELEMENT_IS ("Name"))
+ {
+ if (has_child_of_type (parser->stack_top, MENU_LAYOUT_NODE_NAME))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "Multiple <Name> elements in a <Menu> element is not allowed\n");
+ return;
+ }
+
+ push_node (parser, MENU_LAYOUT_NODE_NAME);
+ }
+ else if (ELEMENT_IS ("Directory"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_DIRECTORY);
+ }
+ else if (ELEMENT_IS ("OnlyUnallocated"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_ONLY_UNALLOCATED);
+ }
+ else if (ELEMENT_IS ("NotOnlyUnallocated"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED);
+ }
+ else if (ELEMENT_IS ("Include"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_INCLUDE);
+ }
+ else if (ELEMENT_IS ("Exclude"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_EXCLUDE);
+ }
+ else if (ELEMENT_IS ("MergeDir"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_MERGE_DIR);
+ }
+ else if (ELEMENT_IS ("KDELegacyDirs"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_KDE_LEGACY_DIRS);
+ }
+ else if (ELEMENT_IS ("Move"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_MOVE);
+ }
+ else if (ELEMENT_IS ("Deleted"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_DELETED);
+
+ }
+ else if (ELEMENT_IS ("NotDeleted"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_NOT_DELETED);
+ }
+ else if (ELEMENT_IS ("Layout"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_LAYOUT);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ "Element <%s> may not appear below <%s>\n",
+ element_name, "Menu");
+ }
+ }
+}
+
+static void
+start_matching_rule_element (MenuParser *parser,
+ GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+
+ if (ELEMENT_IS ("Filename"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_FILENAME);
+ }
+ else if (ELEMENT_IS ("Category"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_CATEGORY);
+ }
+ else if (ELEMENT_IS ("All"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_ALL);
+ }
+ else if (ELEMENT_IS ("And"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_AND);
+ }
+ else if (ELEMENT_IS ("Or"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_OR);
+ }
+ else if (ELEMENT_IS ("Not"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_NOT);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ "Element <%s> may not appear in this context\n",
+ element_name);
+ }
+}
+
+static void
+start_move_child_element (MenuParser *parser,
+ GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ if (ELEMENT_IS ("Old"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_OLD);
+ }
+ else if (ELEMENT_IS ("New"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_NEW);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ "Element <%s> may not appear below <%s>\n",
+ element_name, "Move");
+ }
+}
+
+static void
+start_layout_child_element (MenuParser *parser,
+ GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (ELEMENT_IS ("Menuname"))
+ {
+ const char *show_empty;
+ const char *inline_menus;
+ const char *inline_limit;
+ const char *inline_header;
+ const char *inline_alias;
+
+ push_node (parser, MENU_LAYOUT_NODE_MENUNAME);
+
+ locate_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error,
+ "show_empty", &show_empty,
+ "inline", &inline_menus,
+ "inline_limit", &inline_limit,
+ "inline_header", &inline_header,
+ "inline_alias", &inline_alias,
+ NULL);
+
+ menu_layout_node_menuname_set_values (parser->stack_top,
+ show_empty,
+ inline_menus,
+ inline_limit,
+ inline_header,
+ inline_alias);
+ }
+ else if (ELEMENT_IS ("Merge"))
+ {
+ const char *type;
+
+ push_node (parser, MENU_LAYOUT_NODE_MERGE);
+
+ locate_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error,
+ "type", &type,
+ NULL);
+
+ menu_layout_node_merge_set_type (parser->stack_top, type);
+ }
+ else
+ {
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ if (ELEMENT_IS ("Filename"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_FILENAME);
+ }
+ else if (ELEMENT_IS ("Separator"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_SEPARATOR);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ "Element <%s> may not appear below <%s>\n",
+ element_name, "Move");
+ }
+ }
+}
+
+static void
+start_element_handler (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ MenuParser *parser = user_data;
+
+ if (ELEMENT_IS ("Menu"))
+ {
+ if (parser->stack_top == parser->root &&
+ has_child_of_type (parser->root, MENU_LAYOUT_NODE_MENU))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "Multiple root elements in menu file, only one toplevel <Menu> is allowed\n");
+ return;
+ }
+
+ start_menu_element (parser, context, element_name,
+ attribute_names, attribute_values,
+ error);
+ }
+ else if (parser->stack_top == parser->root)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "Root element in a menu file must be <Menu>, not <%s>\n",
+ element_name);
+ }
+ else if (parser->stack_top->type == MENU_LAYOUT_NODE_MENU)
+ {
+ start_menu_child_element (parser, context, element_name,
+ attribute_names, attribute_values,
+ error);
+ }
+ else if (parser->stack_top->type == MENU_LAYOUT_NODE_INCLUDE ||
+ parser->stack_top->type == MENU_LAYOUT_NODE_EXCLUDE ||
+ parser->stack_top->type == MENU_LAYOUT_NODE_AND ||
+ parser->stack_top->type == MENU_LAYOUT_NODE_OR ||
+ parser->stack_top->type == MENU_LAYOUT_NODE_NOT)
+ {
+ start_matching_rule_element (parser, context, element_name,
+ attribute_names, attribute_values,
+ error);
+ }
+ else if (parser->stack_top->type == MENU_LAYOUT_NODE_MOVE)
+ {
+ start_move_child_element (parser, context, element_name,
+ attribute_names, attribute_values,
+ error);
+ }
+ else if (parser->stack_top->type == MENU_LAYOUT_NODE_LAYOUT ||
+ parser->stack_top->type == MENU_LAYOUT_NODE_DEFAULT_LAYOUT)
+ {
+ start_layout_child_element (parser, context, element_name,
+ attribute_names, attribute_values,
+ error);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ "Element <%s> may not appear in this context\n",
+ element_name);
+ }
+
+ add_context_to_error (error, context);
+}
+
+/* we want to make sure that the <Layout> or <DefaultLayout> is either empty,
+ * or contain one <Merge> of type "all", or contain one <Merge> of type "menus"
+ * and one <Merge> of type "files". If this is not the case, we try to clean up
+ * things:
+ * + if there is at least one <Merge> of type "all", then we only keep the
+ * last <Merge> of type "all" and remove all others <Merge>
+ * + if there is no <Merge> with type "all", we keep only the last <Merge> of
+ * type "menus" and the last <Merge> of type "files". If there's no <Merge>
+ * of type "menus" we append one, and then if there's no <Merge> of type
+ * "files", we append one. (So menus are before files)
+ */
+static gboolean
+fixup_layout_node (GMarkupParseContext *context,
+ MenuParser *parser,
+ MenuLayoutNode *node,
+ GError **error)
+{
+ MenuLayoutNode *child;
+ MenuLayoutNode *last_all;
+ MenuLayoutNode *last_menus;
+ MenuLayoutNode *last_files;
+ int n_all;
+ int n_menus;
+ int n_files;
+
+ if (!node->children)
+ {
+ return TRUE;
+ }
+
+ last_all = NULL;
+ last_menus = NULL;
+ last_files = NULL;
+ n_all = 0;
+ n_menus = 0;
+ n_files = 0;
+
+ child = node->children;
+ while (child != NULL)
+ {
+ switch (child->type)
+ {
+ case MENU_LAYOUT_NODE_MERGE:
+ switch (menu_layout_node_merge_get_type (child))
+ {
+ case MENU_LAYOUT_MERGE_NONE:
+ break;
+
+ case MENU_LAYOUT_MERGE_MENUS:
+ last_menus = child;
+ n_menus++;
+ break;
+
+ case MENU_LAYOUT_MERGE_FILES:
+ last_files = child;
+ n_files++;
+ break;
+
+ case MENU_LAYOUT_MERGE_ALL:
+ last_all = child;
+ n_all++;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ child = node_next (child);
+ }
+
+ if ((n_all == 1 && n_menus == 0 && n_files == 0) ||
+ (n_all == 0 && n_menus == 1 && n_files == 1))
+ {
+ return TRUE;
+ }
+ else if (n_all > 1 || n_menus > 1 || n_files > 1 ||
+ (n_all == 1 && (n_menus != 0 || n_files != 0)))
+ {
+ child = node->children;
+ while (child != NULL)
+ {
+ MenuLayoutNode *next;
+
+ next = node_next (child);
+
+ switch (child->type)
+ {
+ case MENU_LAYOUT_NODE_MERGE:
+ switch (menu_layout_node_merge_get_type (child))
+ {
+ case MENU_LAYOUT_MERGE_NONE:
+ break;
+
+ case MENU_LAYOUT_MERGE_MENUS:
+ if (n_all || last_menus != child)
+ {
+ menu_verbose ("removing duplicated merge menus element\n");
+ menu_layout_node_unlink (child);
+ }
+ break;
+
+ case MENU_LAYOUT_MERGE_FILES:
+ if (n_all || last_files != child)
+ {
+ menu_verbose ("removing duplicated merge files element\n");
+ menu_layout_node_unlink (child);
+ }
+ break;
+
+ case MENU_LAYOUT_MERGE_ALL:
+ if (last_all != child)
+ {
+ menu_verbose ("removing duplicated merge all element\n");
+ menu_layout_node_unlink (child);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ child = next;
+ }
+ }
+
+ if (n_all == 0 && n_menus == 0)
+ {
+ last_menus = menu_layout_node_new (MENU_LAYOUT_NODE_MERGE);
+ menu_layout_node_merge_set_type (last_menus, "menus");
+ menu_verbose ("appending missing merge menus element\n");
+ menu_layout_node_append_child (node, last_menus);
+ }
+
+ if (n_all == 0 && n_files == 0)
+ {
+ last_files = menu_layout_node_new (MENU_LAYOUT_NODE_MERGE);
+ menu_layout_node_merge_set_type (last_files, "files");
+ menu_verbose ("appending missing merge files element\n");
+ menu_layout_node_append_child (node, last_files);
+ }
+
+ return TRUE;
+}
+
+/* we want to a) check that we have old-new pairs and b) canonicalize
+ * such that each <Move> has exactly one old-new pair
+ */
+static gboolean
+fixup_move_node (GMarkupParseContext *context,
+ MenuParser *parser,
+ MenuLayoutNode *node,
+ GError **error)
+{
+ MenuLayoutNode *child;
+ int n_old;
+ int n_new;
+
+ n_old = 0;
+ n_new = 0;
+
+ child = node->children;
+ while (child != NULL)
+ {
+ switch (child->type)
+ {
+ case MENU_LAYOUT_NODE_OLD:
+ if (n_new != n_old)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "<Old>/<New> elements not paired properly\n");
+ return FALSE;
+ }
+
+ n_old += 1;
+
+ break;
+
+ case MENU_LAYOUT_NODE_NEW:
+ n_new += 1;
+
+ if (n_new != n_old)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "<Old>/<New> elements not paired properly\n");
+ return FALSE;
+ }
+
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ child = node_next (child);
+ }
+
+ if (n_new == 0 || n_old == 0)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "<Old>/<New> elements missing under <Move>\n");
+ return FALSE;
+ }
+
+ g_assert (n_new == n_old);
+ g_assert ((n_new + n_old) % 2 == 0);
+
+ if (n_new > 1)
+ {
+ MenuLayoutNode *prev;
+ MenuLayoutNode *append_after;
+
+ /* Need to split the <Move> into multiple <Move> */
+
+ n_old = 0;
+ n_new = 0;
+ prev = NULL;
+ append_after = node;
+
+ child = node->children;
+ while (child != NULL)
+ {
+ MenuLayoutNode *next;
+
+ next = node_next (child);
+
+ switch (child->type)
+ {
+ case MENU_LAYOUT_NODE_OLD:
+ n_old += 1;
+ break;
+
+ case MENU_LAYOUT_NODE_NEW:
+ n_new += 1;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ if (n_old == n_new &&
+ n_old > 1)
+ {
+ /* Move the just-completed pair */
+ MenuLayoutNode *new_move;
+
+ g_assert (prev != NULL);
+
+ new_move = menu_layout_node_new (MENU_LAYOUT_NODE_MOVE);
+ menu_verbose ("inserting new_move after append_after\n");
+ menu_layout_node_insert_after (append_after, new_move);
+ append_after = new_move;
+
+ menu_layout_node_steal (prev);
+ menu_layout_node_steal (child);
+
+ menu_verbose ("appending prev to new_move\n");
+ menu_layout_node_append_child (new_move, prev);
+ menu_verbose ("appending child to new_move\n");
+ menu_layout_node_append_child (new_move, child);
+
+ menu_verbose ("Created new move element old = %s new = %s\n",
+ menu_layout_node_move_get_old (new_move),
+ menu_layout_node_move_get_new (new_move));
+
+ menu_layout_node_unref (new_move);
+ menu_layout_node_unref (prev);
+ menu_layout_node_unref (child);
+
+ prev = NULL;
+ }
+ else
+ {
+ prev = child;
+ }
+
+ prev = child;
+ child = next;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+end_element_handler (GMarkupParseContext *context,
+ const char *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ MenuParser *parser = user_data;
+
+ g_assert (parser->stack_top != NULL);
+
+ switch (parser->stack_top->type)
+ {
+ case MENU_LAYOUT_NODE_APP_DIR:
+ case MENU_LAYOUT_NODE_DIRECTORY_DIR:
+ case MENU_LAYOUT_NODE_NAME:
+ case MENU_LAYOUT_NODE_DIRECTORY:
+ case MENU_LAYOUT_NODE_FILENAME:
+ case MENU_LAYOUT_NODE_CATEGORY:
+ case MENU_LAYOUT_NODE_MERGE_DIR:
+ case MENU_LAYOUT_NODE_LEGACY_DIR:
+ case MENU_LAYOUT_NODE_OLD:
+ case MENU_LAYOUT_NODE_NEW:
+ case MENU_LAYOUT_NODE_MENUNAME:
+ if (menu_layout_node_get_content (parser->stack_top) == NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+ "Element <%s> is required to contain text and was empty\n",
+ element_name);
+ goto out;
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_MENU:
+ if (!has_child_of_type (parser->stack_top, MENU_LAYOUT_NODE_NAME))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "<Menu> elements are required to contain a <Name> element\n");
+ goto out;
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_ROOT:
+ case MENU_LAYOUT_NODE_PASSTHROUGH:
+ case MENU_LAYOUT_NODE_DEFAULT_APP_DIRS:
+ case MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS:
+ case MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS:
+ case MENU_LAYOUT_NODE_ONLY_UNALLOCATED:
+ case MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED:
+ case MENU_LAYOUT_NODE_INCLUDE:
+ case MENU_LAYOUT_NODE_EXCLUDE:
+ case MENU_LAYOUT_NODE_ALL:
+ case MENU_LAYOUT_NODE_AND:
+ case MENU_LAYOUT_NODE_OR:
+ case MENU_LAYOUT_NODE_NOT:
+ case MENU_LAYOUT_NODE_KDE_LEGACY_DIRS:
+ case MENU_LAYOUT_NODE_DELETED:
+ case MENU_LAYOUT_NODE_NOT_DELETED:
+ case MENU_LAYOUT_NODE_SEPARATOR:
+ case MENU_LAYOUT_NODE_MERGE:
+ case MENU_LAYOUT_NODE_MERGE_FILE:
+ break;
+
+ case MENU_LAYOUT_NODE_LAYOUT:
+ case MENU_LAYOUT_NODE_DEFAULT_LAYOUT:
+ if (!fixup_layout_node (context, parser, parser->stack_top, error))
+ goto out;
+ break;
+
+ case MENU_LAYOUT_NODE_MOVE:
+ if (!fixup_move_node (context, parser, parser->stack_top, error))
+ goto out;
+ break;
+ }
+
+ out:
+ parser->stack_top = parser->stack_top->parent;
+}
+
+static gboolean
+all_whitespace (const char *text,
+ int text_len)
+{
+ const char *p;
+ const char *end;
+
+ p = text;
+ end = text + text_len;
+
+ while (p != end)
+ {
+ if (!g_ascii_isspace (*p))
+ return FALSE;
+
+ p = g_utf8_next_char (p);
+ }
+
+ return TRUE;
+}
+
+static void
+text_handler (GMarkupParseContext *context,
+ const char *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ MenuParser *parser = user_data;
+
+ switch (parser->stack_top->type)
+ {
+ case MENU_LAYOUT_NODE_APP_DIR:
+ case MENU_LAYOUT_NODE_DIRECTORY_DIR:
+ case MENU_LAYOUT_NODE_NAME:
+ case MENU_LAYOUT_NODE_DIRECTORY:
+ case MENU_LAYOUT_NODE_FILENAME:
+ case MENU_LAYOUT_NODE_CATEGORY:
+ case MENU_LAYOUT_NODE_MERGE_FILE:
+ case MENU_LAYOUT_NODE_MERGE_DIR:
+ case MENU_LAYOUT_NODE_LEGACY_DIR:
+ case MENU_LAYOUT_NODE_OLD:
+ case MENU_LAYOUT_NODE_NEW:
+ case MENU_LAYOUT_NODE_MENUNAME:
+ g_assert (menu_layout_node_get_content (parser->stack_top) == NULL);
+
+ menu_layout_node_set_content (parser->stack_top, text);
+ break;
+
+ case MENU_LAYOUT_NODE_ROOT:
+ case MENU_LAYOUT_NODE_PASSTHROUGH:
+ case MENU_LAYOUT_NODE_MENU:
+ case MENU_LAYOUT_NODE_DEFAULT_APP_DIRS:
+ case MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS:
+ case MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS:
+ case MENU_LAYOUT_NODE_ONLY_UNALLOCATED:
+ case MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED:
+ case MENU_LAYOUT_NODE_INCLUDE:
+ case MENU_LAYOUT_NODE_EXCLUDE:
+ case MENU_LAYOUT_NODE_ALL:
+ case MENU_LAYOUT_NODE_AND:
+ case MENU_LAYOUT_NODE_OR:
+ case MENU_LAYOUT_NODE_NOT:
+ case MENU_LAYOUT_NODE_KDE_LEGACY_DIRS:
+ case MENU_LAYOUT_NODE_MOVE:
+ case MENU_LAYOUT_NODE_DELETED:
+ case MENU_LAYOUT_NODE_NOT_DELETED:
+ case MENU_LAYOUT_NODE_LAYOUT:
+ case MENU_LAYOUT_NODE_DEFAULT_LAYOUT:
+ case MENU_LAYOUT_NODE_SEPARATOR:
+ case MENU_LAYOUT_NODE_MERGE:
+ if (!all_whitespace (text, text_len))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "No text is allowed inside element <%s>",
+ g_markup_parse_context_get_element (context));
+ }
+ break;
+ }
+
+ add_context_to_error (error, context);
+}
+
+static void
+passthrough_handler (GMarkupParseContext *context,
+ const char *passthrough_text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ MenuParser *parser = user_data;
+ MenuLayoutNode *node;
+
+ /* don't push passthrough on the stack, it's not an element */
+
+ node = menu_layout_node_new (MENU_LAYOUT_NODE_PASSTHROUGH);
+ menu_layout_node_set_content (node, passthrough_text);
+
+ menu_layout_node_append_child (parser->stack_top, node);
+ menu_layout_node_unref (node);
+
+ add_context_to_error (error, context);
+}
+
+static void
+menu_parser_init (MenuParser *parser)
+{
+ parser->root = menu_layout_node_new (MENU_LAYOUT_NODE_ROOT);
+ parser->stack_top = parser->root;
+}
+
+static void
+menu_parser_free (MenuParser *parser)
+{
+ if (parser->root)
+ menu_layout_node_unref (parser->root);
+}
+
+MenuLayoutNode *
+menu_layout_load (const char *filename,
+ const char *non_prefixed_basename,
+ GError **err)
+{
+ GMarkupParseContext *context;
+ MenuLayoutNodeRoot *root;
+ MenuLayoutNode *retval;
+ MenuParser parser;
+ GError *error;
+ GString *str;
+ char *text;
+ char *s;
+ gsize length;
+
+ text = NULL;
+ length = 0;
+ retval = NULL;
+ context = NULL;
+
+ menu_verbose ("Loading \"%s\" from disk\n", filename);
+
+ if (!g_file_get_contents (filename,
+ &text,
+ &length,
+ err))
+ {
+ menu_verbose ("Failed to load \"%s\"\n",
+ filename);
+ return NULL;
+ }
+
+ g_assert (text != NULL);
+
+ menu_parser_init (&parser);
+
+ root = (MenuLayoutNodeRoot *) parser.root;
+
+ root->basedir = g_path_get_dirname (filename);
+ menu_verbose ("Set basedir \"%s\"\n", root->basedir);
+
+ if (non_prefixed_basename)
+ s = g_strdup (non_prefixed_basename);
+ else
+ s = g_path_get_basename (filename);
+ str = g_string_new (s);
+ if (g_str_has_suffix (str->str, ".menu"))
+ g_string_truncate (str, str->len - strlen (".menu"));
+
+ root->name = str->str;
+ menu_verbose ("Set menu name \"%s\"\n", root->name);
+
+ g_string_free (str, FALSE);
+ g_free (s);
+
+ context = g_markup_parse_context_new (&menu_funcs, 0, &parser, NULL);
+
+ error = NULL;
+ if (!g_markup_parse_context_parse (context,
+ text,
+ length,
+ &error))
+ goto out;
+
+ error = NULL;
+ g_markup_parse_context_end_parse (context, &error);
+
+ out:
+ if (context)
+ g_markup_parse_context_free (context);
+ g_free (text);
+
+ if (error)
+ {
+ menu_verbose ("Error \"%s\" loading \"%s\"\n",
+ error->message, filename);
+ g_propagate_error (err, error);
+ }
+ else if (has_child_of_type (parser.root, MENU_LAYOUT_NODE_MENU))
+ {
+ menu_verbose ("File loaded OK\n");
+ retval = parser.root;
+ parser.root = NULL;
+ }
+ else
+ {
+ menu_verbose ("Did not have a root element in file\n");
+ g_set_error (err, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "Menu file %s did not contain a root <Menu> element",
+ filename);
+ }
+
+ menu_parser_free (&parser);
+
+ return retval;
+}
diff --git a/libmenu/menu-layout.h b/libmenu/menu-layout.h
new file mode 100644
index 0000000..4b63014
--- /dev/null
+++ b/libmenu/menu-layout.h
@@ -0,0 +1,161 @@
+/* Menu layout in-memory data structure (a custom "DOM tree") */
+
+/*
+ * Copyright (C) 2002 - 2004 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __MENU_LAYOUT_H__
+#define __MENU_LAYOUT_H__
+
+#include <glib.h>
+
+#include "entry-directories.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct MenuLayoutNode MenuLayoutNode;
+
+typedef enum {
+ MENU_LAYOUT_NODE_ROOT,
+ MENU_LAYOUT_NODE_PASSTHROUGH,
+ MENU_LAYOUT_NODE_MENU,
+ MENU_LAYOUT_NODE_APP_DIR,
+ MENU_LAYOUT_NODE_DEFAULT_APP_DIRS,
+ MENU_LAYOUT_NODE_DIRECTORY_DIR,
+ MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS,
+ MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS,
+ MENU_LAYOUT_NODE_NAME,
+ MENU_LAYOUT_NODE_DIRECTORY,
+ MENU_LAYOUT_NODE_ONLY_UNALLOCATED,
+ MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED,
+ MENU_LAYOUT_NODE_INCLUDE,
+ MENU_LAYOUT_NODE_EXCLUDE,
+ MENU_LAYOUT_NODE_FILENAME,
+ MENU_LAYOUT_NODE_CATEGORY,
+ MENU_LAYOUT_NODE_ALL,
+ MENU_LAYOUT_NODE_AND,
+ MENU_LAYOUT_NODE_OR,
+ MENU_LAYOUT_NODE_NOT,
+ MENU_LAYOUT_NODE_MERGE_FILE,
+ MENU_LAYOUT_NODE_MERGE_DIR,
+ MENU_LAYOUT_NODE_LEGACY_DIR,
+ MENU_LAYOUT_NODE_KDE_LEGACY_DIRS,
+ MENU_LAYOUT_NODE_MOVE,
+ MENU_LAYOUT_NODE_OLD,
+ MENU_LAYOUT_NODE_NEW,
+ MENU_LAYOUT_NODE_DELETED,
+ MENU_LAYOUT_NODE_NOT_DELETED,
+ MENU_LAYOUT_NODE_LAYOUT,
+ MENU_LAYOUT_NODE_DEFAULT_LAYOUT,
+ MENU_LAYOUT_NODE_MENUNAME,
+ MENU_LAYOUT_NODE_SEPARATOR,
+ MENU_LAYOUT_NODE_MERGE
+} MenuLayoutNodeType;
+
+typedef enum {
+ MENU_MERGE_FILE_TYPE_PATH = 0,
+ MENU_MERGE_FILE_TYPE_PARENT
+} MenuMergeFileType;
+
+typedef enum {
+ MENU_LAYOUT_MERGE_NONE,
+ MENU_LAYOUT_MERGE_MENUS,
+ MENU_LAYOUT_MERGE_FILES,
+ MENU_LAYOUT_MERGE_ALL
+} MenuLayoutMergeType;
+
+typedef enum {
+ MENU_LAYOUT_VALUES_NONE = 0,
+ MENU_LAYOUT_VALUES_SHOW_EMPTY = 1 << 0,
+ MENU_LAYOUT_VALUES_INLINE_MENUS = 1 << 1,
+ MENU_LAYOUT_VALUES_INLINE_LIMIT = 1 << 2,
+ MENU_LAYOUT_VALUES_INLINE_HEADER = 1 << 3,
+ MENU_LAYOUT_VALUES_INLINE_ALIAS = 1 << 4
+} MenuLayoutValuesMask;
+
+typedef struct {
+ MenuLayoutValuesMask mask;
+
+ guint show_empty: 1;
+ guint inline_menus: 1;
+ guint inline_header: 1;
+ guint inline_alias: 1;
+
+ guint inline_limit;
+} MenuLayoutValues;
+
+
+MenuLayoutNode *menu_layout_load (const char* filename, const char *non_prefixed_basename, GError** error);
+
+MenuLayoutNode *menu_layout_node_new (MenuLayoutNodeType type);
+MenuLayoutNode *menu_layout_node_ref (MenuLayoutNode *node);
+void menu_layout_node_unref (MenuLayoutNode *node);
+
+MenuLayoutNodeType menu_layout_node_get_type (MenuLayoutNode *node);
+
+MenuLayoutNode *menu_layout_node_get_root (MenuLayoutNode *node);
+MenuLayoutNode *menu_layout_node_get_parent (MenuLayoutNode *node);
+MenuLayoutNode *menu_layout_node_get_children (MenuLayoutNode *node);
+MenuLayoutNode *menu_layout_node_get_next (MenuLayoutNode *node);
+
+void menu_layout_node_insert_before (MenuLayoutNode *node, MenuLayoutNode *new_sibling);
+void menu_layout_node_insert_after (MenuLayoutNode *node, MenuLayoutNode *new_sibling);
+void menu_layout_node_prepend_child (MenuLayoutNode *parent, MenuLayoutNode *new_child);
+void menu_layout_node_append_child (MenuLayoutNode *parent, MenuLayoutNode *new_child);
+
+void menu_layout_node_unlink (MenuLayoutNode *node);
+void menu_layout_node_steal (MenuLayoutNode *node);
+
+const char *menu_layout_node_get_content (MenuLayoutNode *node);
+void menu_layout_node_set_content (MenuLayoutNode *node, const char *content);
+
+char *menu_layout_node_get_content_as_path (MenuLayoutNode *node);
+
+const char *menu_layout_node_root_get_name (MenuLayoutNode *node);
+const char *menu_layout_node_root_get_basedir (MenuLayoutNode *node);
+
+const char *menu_layout_node_menu_get_name (MenuLayoutNode *node);
+EntryDirectoryList *menu_layout_node_menu_get_app_dirs (MenuLayoutNode *node);
+EntryDirectoryList *menu_layout_node_menu_get_directory_dirs (MenuLayoutNode *node);
+
+const char *menu_layout_node_move_get_old (MenuLayoutNode *node);
+const char *menu_layout_node_move_get_new (MenuLayoutNode *node);
+
+const char *menu_layout_node_legacy_dir_get_prefix (MenuLayoutNode *node);
+void menu_layout_node_legacy_dir_set_prefix (MenuLayoutNode *node, const char *prefix);
+
+MenuMergeFileType menu_layout_node_merge_file_get_type (MenuLayoutNode *node);
+void menu_layout_node_merge_file_set_type (MenuLayoutNode *node, MenuMergeFileType type);
+
+MenuLayoutMergeType menu_layout_node_merge_get_type (MenuLayoutNode *node);
+
+void menu_layout_node_default_layout_get_values (MenuLayoutNode *node, MenuLayoutValues *values);
+void menu_layout_node_menuname_get_values (MenuLayoutNode *node, MenuLayoutValues *values);
+
+typedef void (*MenuLayoutNodeEntriesChangedFunc) (MenuLayoutNode* node, gpointer user_data);
+
+void menu_layout_node_root_add_entries_monitor (MenuLayoutNode* node, MenuLayoutNodeEntriesChangedFunc callback, gpointer user_data);
+void menu_layout_node_root_remove_entries_monitor (MenuLayoutNode* node, MenuLayoutNodeEntriesChangedFunc callback, gpointer user_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MENU_LAYOUT_H__ */
diff --git a/libmenu/menu-monitor.c b/libmenu/menu-monitor.c
new file mode 100644
index 0000000..ff36010
--- /dev/null
+++ b/libmenu/menu-monitor.c
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2005 Red Hat, Inc.
+ * Copyright (C) 2006 Mark McLoughlin
+ * Copyright (C) 2007 Sebastian Dröge
+ * Copyright (C) 2008 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include "menu-monitor.h"
+
+#include <gio/gio.h>
+
+#include "menu-util.h"
+
+struct MenuMonitor {
+ char* path;
+ guint refcount;
+
+ GSList* notifies;
+
+ GFileMonitor* monitor;
+
+ guint is_directory: 1;
+};
+
+typedef struct {
+ MenuMonitor* monitor;
+ MenuMonitorEvent event;
+ char* path;
+} MenuMonitorEventInfo;
+
+typedef struct {
+ MenuMonitorNotifyFunc notify_func;
+ gpointer user_data;
+ guint refcount;
+} MenuMonitorNotify;
+
+static MenuMonitorNotify* mate_menu_monitor_notify_ref(MenuMonitorNotify* notify);
+static void mate_menu_monitor_notify_unref(MenuMonitorNotify* notify);
+
+static GHashTable* monitors_registry = NULL;
+static guint events_idle_handler = 0;
+static GSList* pending_events = NULL;
+
+static void invoke_notifies(MenuMonitor* monitor, MenuMonitorEvent event, const char* path)
+{
+ GSList *copy;
+ GSList *tmp;
+
+ copy = g_slist_copy (monitor->notifies);
+ g_slist_foreach (copy,
+ (GFunc) mate_menu_monitor_notify_ref,
+ NULL);
+
+ tmp = copy;
+ while (tmp != NULL)
+ {
+ MenuMonitorNotify *notify = tmp->data;
+ GSList *next = tmp->next;
+
+ if (notify->notify_func)
+ {
+ notify->notify_func (monitor, event, path, notify->user_data);
+ }
+
+ mate_menu_monitor_notify_unref(notify);
+
+ tmp = next;
+ }
+
+ g_slist_free (copy);
+}
+
+static gboolean emit_events_in_idle(void)
+{
+ GSList *events_to_emit;
+ GSList *tmp;
+
+ events_to_emit = pending_events;
+
+ pending_events = NULL;
+ events_idle_handler = 0;
+
+ tmp = events_to_emit;
+ while (tmp != NULL)
+ {
+ MenuMonitorEventInfo *event_info = tmp->data;
+
+ mate_menu_monitor_ref(event_info->monitor);
+
+ tmp = tmp->next;
+ }
+
+ tmp = events_to_emit;
+ while (tmp != NULL)
+ {
+ MenuMonitorEventInfo *event_info = tmp->data;
+
+ invoke_notifies (event_info->monitor,
+ event_info->event,
+ event_info->path);
+
+ menu_monitor_unref (event_info->monitor);
+ event_info->monitor = NULL;
+
+ g_free (event_info->path);
+ event_info->path = NULL;
+
+ event_info->event = MENU_MONITOR_EVENT_INVALID;
+
+ g_free (event_info);
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (events_to_emit);
+
+ return FALSE;
+}
+
+static void menu_monitor_queue_event(MenuMonitorEventInfo* event_info)
+{
+ pending_events = g_slist_append (pending_events, event_info);
+
+ if (events_idle_handler == 0)
+ {
+ events_idle_handler = g_idle_add ((GSourceFunc) emit_events_in_idle, NULL);
+ }
+}
+
+static inline char* get_registry_key(const char* path, gboolean is_directory)
+{
+ return g_strdup_printf ("%s:%s",
+ path,
+ is_directory ? "<dir>" : "<file>");
+}
+
+static gboolean monitor_callback (GFileMonitor* monitor, GFile* child, GFile* other_file, GFileMonitorEvent eflags, gpointer user_data)
+{
+ MenuMonitorEventInfo *event_info;
+ MenuMonitorEvent event;
+ MenuMonitor *menu_monitor = (MenuMonitor *) user_data;
+
+ event = MENU_MONITOR_EVENT_INVALID;
+ switch (eflags)
+ {
+ case G_FILE_MONITOR_EVENT_CHANGED:
+ event = MENU_MONITOR_EVENT_CHANGED;
+ break;
+ case G_FILE_MONITOR_EVENT_CREATED:
+ event = MENU_MONITOR_EVENT_CREATED;
+ break;
+ case G_FILE_MONITOR_EVENT_DELETED:
+ event = MENU_MONITOR_EVENT_DELETED;
+ break;
+ default:
+ return TRUE;
+ }
+
+ event_info = g_new0 (MenuMonitorEventInfo, 1);
+
+ event_info->path = g_file_get_path (child);
+ event_info->event = event;
+ event_info->monitor = menu_monitor;
+
+ menu_monitor_queue_event (event_info);
+
+ return TRUE;
+}
+
+static MenuMonitor* register_monitor(const char* path, gboolean is_directory)
+{
+ static gboolean initted = FALSE;
+ MenuMonitor *retval;
+ GFile *file;
+
+ if (!initted)
+ {
+ /* This is the only place where we're using GObject and the app can't
+ * know we're using it, so we need to init the type system ourselves. */
+ g_type_init ();
+ initted = TRUE;
+ }
+
+ retval = g_new0 (MenuMonitor, 1);
+
+ retval->path = g_strdup (path);
+ retval->refcount = 1;
+ retval->is_directory = is_directory != FALSE;
+
+ file = g_file_new_for_path (retval->path);
+
+ if (file == NULL)
+ {
+ menu_verbose ("Not adding monitor on '%s', failed to create GFile\n",
+ retval->path);
+ return retval;
+ }
+
+ if (retval->is_directory)
+ retval->monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE,
+ NULL, NULL);
+ else
+ retval->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE,
+ NULL, NULL);
+
+ g_object_unref (G_OBJECT (file));
+
+ if (retval->monitor == NULL)
+ {
+ menu_verbose ("Not adding monitor on '%s', failed to create monitor\n",
+ retval->path);
+ return retval;
+ }
+
+ g_signal_connect (retval->monitor, "changed",
+ G_CALLBACK (monitor_callback), retval);
+
+ return retval;
+}
+
+static MenuMonitor* lookup_monitor(const char* path, gboolean is_directory)
+{
+ MenuMonitor *retval;
+ char *registry_key;
+
+ retval = NULL;
+
+ registry_key = get_registry_key (path, is_directory);
+
+ if (monitors_registry == NULL)
+ {
+ monitors_registry = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
+ }
+ else
+ {
+ retval = g_hash_table_lookup (monitors_registry, registry_key);
+ }
+
+ if (retval == NULL)
+ {
+ retval = register_monitor (path, is_directory);
+ g_hash_table_insert (monitors_registry, registry_key, retval);
+
+ return retval;
+ }
+ else
+ {
+ g_free (registry_key);
+
+ return mate_menu_monitor_ref(retval);
+ }
+}
+
+MenuMonitor* mate_menu_monitor_file_get(const char* path)
+{
+ g_return_val_if_fail(path != NULL, NULL);
+
+ return lookup_monitor(path, FALSE);
+}
+
+MenuMonitor* menu_get_directory_monitor(const char* path)
+{
+ g_return_val_if_fail (path != NULL, NULL);
+
+ return lookup_monitor (path, TRUE);
+}
+
+MenuMonitor* mate_menu_monitor_ref(MenuMonitor* monitor)
+{
+ g_return_val_if_fail(monitor != NULL, NULL);
+ g_return_val_if_fail(monitor->refcount > 0, NULL);
+
+ monitor->refcount++;
+
+ return monitor;
+}
+
+static void menu_monitor_clear_pending_events(MenuMonitor* monitor)
+{
+ GSList *tmp;
+
+ tmp = pending_events;
+ while (tmp != NULL)
+ {
+ MenuMonitorEventInfo *event_info = tmp->data;
+ GSList *next = tmp->next;
+
+ if (event_info->monitor == monitor)
+ {
+ pending_events = g_slist_delete_link (pending_events, tmp);
+
+ g_free (event_info->path);
+ event_info->path = NULL;
+
+ event_info->monitor = NULL;
+ event_info->event = MENU_MONITOR_EVENT_INVALID;
+
+ g_free (event_info);
+ }
+
+ tmp = next;
+ }
+}
+
+void menu_monitor_unref(MenuMonitor* monitor)
+{
+ char *registry_key;
+
+ g_return_if_fail (monitor != NULL);
+ g_return_if_fail (monitor->refcount > 0);
+
+ if (--monitor->refcount > 0)
+ return;
+
+ registry_key = get_registry_key (monitor->path, monitor->is_directory);
+ g_hash_table_remove (monitors_registry, registry_key);
+ g_free (registry_key);
+
+ if (g_hash_table_size (monitors_registry) == 0)
+ {
+ g_hash_table_destroy (monitors_registry);
+ monitors_registry = NULL;
+ }
+
+ if (monitor->monitor)
+ {
+ g_file_monitor_cancel (monitor->monitor);
+ g_object_unref (monitor->monitor);
+ monitor->monitor = NULL;
+ }
+
+ g_slist_foreach (monitor->notifies, (GFunc) mate_menu_monitor_notify_unref, NULL);
+ g_slist_free (monitor->notifies);
+ monitor->notifies = NULL;
+
+ menu_monitor_clear_pending_events (monitor);
+
+ g_free (monitor->path);
+ monitor->path = NULL;
+
+ g_free (monitor);
+}
+
+static MenuMonitorNotify* mate_menu_monitor_notify_ref(MenuMonitorNotify* notify)
+{
+ g_return_val_if_fail(notify != NULL, NULL);
+ g_return_val_if_fail(notify->refcount > 0, NULL);
+
+ notify->refcount++;
+
+ return notify;
+}
+
+static void mate_menu_monitor_notify_unref(MenuMonitorNotify* notify)
+{
+ g_return_if_fail(notify != NULL);
+ g_return_if_fail(notify->refcount > 0);
+
+ if (--notify->refcount > 0)
+ {
+ return;
+ }
+
+ g_free(notify);
+}
+
+void menu_monitor_add_notify(MenuMonitor* monitor, MenuMonitorNotifyFunc notify_func, gpointer user_data)
+{
+ MenuMonitorNotify* notify;
+
+ g_return_if_fail(monitor != NULL);
+ g_return_if_fail(notify_func != NULL);
+
+ GSList* tmp = monitor->notifies;
+
+ while (tmp != NULL)
+ {
+ notify = tmp->data;
+
+ if (notify->notify_func == notify_func && notify->user_data == user_data)
+ {
+ break;
+ }
+
+ tmp = tmp->next;
+ }
+
+ if (tmp == NULL)
+ {
+ notify = g_new0(MenuMonitorNotify, 1);
+ notify->notify_func = notify_func;
+ notify->user_data = user_data;
+ notify->refcount = 1;
+
+ monitor->notifies = g_slist_append(monitor->notifies, notify);
+ }
+}
+
+void mate_menu_monitor_notify_remove(MenuMonitor* monitor, MenuMonitorNotifyFunc notify_func, gpointer user_data)
+{
+ GSList* tmp = monitor->notifies;
+
+ while (tmp != NULL)
+ {
+ MenuMonitorNotify* notify = tmp->data;
+ GSList* next = tmp->next;
+
+ if (notify->notify_func == notify_func && notify->user_data == user_data)
+ {
+ notify->notify_func = NULL;
+ notify->user_data = NULL;
+
+ mate_menu_monitor_notify_unref(notify);
+
+ monitor->notifies = g_slist_delete_link(monitor->notifies, tmp);
+ }
+
+ tmp = next;
+ }
+}
diff --git a/libmenu/menu-monitor.h b/libmenu/menu-monitor.h
new file mode 100644
index 0000000..1dbf6ba
--- /dev/null
+++ b/libmenu/menu-monitor.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2005 Red Hat, Inc.
+ * Copyright (C) 2011 Perberos
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __MENU_MONITOR_H__
+#define __MENU_MONITOR_H__
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct MenuMonitor MenuMonitor;
+
+typedef enum {
+ MENU_MONITOR_EVENT_INVALID = 0,
+ MENU_MONITOR_EVENT_CREATED = 1,
+ MENU_MONITOR_EVENT_DELETED = 2,
+ MENU_MONITOR_EVENT_CHANGED = 3
+} MenuMonitorEvent;
+
+typedef void (*MenuMonitorNotifyFunc) (MenuMonitor* monitor, MenuMonitorEvent event, const char* path, gpointer user_data);
+
+
+MenuMonitor* menu_get_file_monitor(const char* path);
+MenuMonitor* menu_get_directory_monitor(const char* path);
+
+MenuMonitor* menu_monitor_ref(MenuMonitor* monitor);
+void menu_monitor_unref(MenuMonitor* monitor);
+
+void menu_monitor_add_notify(MenuMonitor* monitor, MenuMonitorNotifyFunc notify_func, gpointer user_data);
+void menu_monitor_remove_notify(MenuMonitor* monitor, MenuMonitorNotifyFunc notify_func, gpointer user_data);
+
+
+/* Izquierda a derecha
+ */
+
+#define mate_menu_monitor_file_get menu_get_file_monitor
+#define mate_menu_monitor_directory_get menu_get_directory_monitor
+
+#define mate_menu_monitor_ref menu_monitor_ref
+#define mate_menu_monitor_unref menu_monitor_unref
+
+#define mate_menu_monitor_notify_add menu_monitor_add_notify
+#define mate_menu_monitor_notify_remove menu_monitor_remove_notify
+#define mate_menu_monitor_notify_ref menu_monitor_notify_ref /* private */
+#define mate_menu_monitor_notify_unref menu_monitor_notify_unref /* private */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MENU_MONITOR_H__ */
diff --git a/libmenu/menu-util.c b/libmenu/menu-util.c
new file mode 100644
index 0000000..2f992b0
--- /dev/null
+++ b/libmenu/menu-util.c
@@ -0,0 +1,436 @@
+/* Random utility functions for menu code */
+
+/*
+ * Copyright (C) 2003 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include "menu-util.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+
+
+#ifdef G_ENABLE_DEBUG
+
+static gboolean verbose = FALSE;
+static gboolean initted = FALSE;
+
+static inline gboolean menu_verbose_enabled(void)
+{
+ if (!initted)
+ {
+ verbose = g_getenv("MENU_VERBOSE") != NULL;
+ initted = TRUE;
+ }
+
+ return verbose;
+}
+
+static int utf8_fputs(const char* str, FILE* f)
+{
+ char* l;
+ int ret;
+
+ l = g_locale_from_utf8(str, -1, NULL, NULL, NULL);
+
+ if (l == NULL)
+ {
+ ret = fputs(str, f); /* just print it anyway, better than nothing */
+ }
+ else
+ {
+ ret = fputs(l, f);
+ }
+
+ g_free(l);
+
+ return ret;
+}
+
+void menu_verbose(const char* format, ...)
+{
+ va_list args;
+ char* str;
+
+ if (!menu_verbose_enabled())
+ {
+ return;
+ }
+
+ va_start(args, format);
+ str = g_strdup_vprintf(format, args);
+ va_end(args);
+
+ utf8_fputs(str, stderr);
+ fflush(stderr);
+
+ g_free(str);
+}
+
+static void append_to_string(MenuLayoutNode* node, gboolean onelevel, int depth, GString* str);
+
+static void append_spaces(GString* str, int depth)
+{
+ while (depth > 0)
+ {
+ g_string_append_c(str, ' ');
+ --depth;
+ }
+}
+
+static void append_children(MenuLayoutNode* node, int depth, GString* str)
+{
+ MenuLayoutNode* iter;
+
+ iter = menu_layout_node_get_children(node);
+
+ while (iter != NULL)
+ {
+ append_to_string(iter, FALSE, depth, str);
+
+ iter = menu_layout_node_get_next(iter);
+ }
+}
+
+static void append_simple_with_attr(MenuLayoutNode* node, int depth, const char* node_name, const char* attr_name, const char* attr_value, GString* str)
+{
+ const char* content;
+
+ append_spaces(str, depth);
+
+ if ((content = menu_layout_node_get_content(node)))
+ {
+ char* escaped;
+
+ escaped = g_markup_escape_text(content, -1);
+
+ if (attr_name && attr_value)
+ {
+ char* attr_escaped;
+
+ attr_escaped = g_markup_escape_text(attr_value, -1);
+
+ g_string_append_printf(str, "<%s %s=\"%s\">%s</%s>\n", node_name, attr_name, attr_escaped, escaped, node_name);
+
+ g_free(attr_escaped);
+ }
+ else
+ {
+ g_string_append_printf(str, "<%s>%s</%s>\n", node_name, escaped, node_name);
+ }
+
+ g_free(escaped);
+ }
+ else
+ {
+ if (attr_name && attr_value)
+ {
+ char* attr_escaped;
+
+ attr_escaped = g_markup_escape_text(attr_value, -1);
+
+ g_string_append_printf(str, "<%s %s=\"%s\"/>\n", node_name, attr_name, attr_escaped);
+
+ g_free(attr_escaped);
+ }
+ else
+ {
+ g_string_append_printf(str, "<%s/>\n", node_name);
+ }
+ }
+}
+
+static void append_layout(MenuLayoutNode* node, int depth, const char* node_name, MenuLayoutValues* layout_values, GString* str)
+{
+ const char* content;
+
+ append_spaces(str, depth);
+
+ if ((content = menu_layout_node_get_content(node)))
+ {
+ char* escaped;
+
+ escaped = g_markup_escape_text(content, -1);
+
+ g_string_append_printf(str,
+ "<%s show_empty=\"%s\" inline=\"%s\" inline_header=\"%s\""
+ " inline_alias=\"%s\" inline_limit=\"%d\">%s</%s>\n",
+ node_name,
+ layout_values->show_empty ? "true" : "false",
+ layout_values->inline_menus ? "true" : "false",
+ layout_values->inline_header ? "true" : "false",
+ layout_values->inline_alias ? "true" : "false",
+ layout_values->inline_limit,
+ escaped,
+ node_name);
+
+ g_free(escaped);
+ }
+ else
+ {
+ g_string_append_printf(str,
+ "<%s show_empty=\"%s\" inline=\"%s\" inline_header=\"%s\""
+ " inline_alias=\"%s\" inline_limit=\"%d\"/>\n",
+ node_name,
+ layout_values->show_empty ? "true" : "false",
+ layout_values->inline_menus ? "true" : "false",
+ layout_values->inline_header ? "true" : "false",
+ layout_values->inline_alias ? "true" : "false",
+ layout_values->inline_limit);
+ }
+}
+
+static void append_merge(MenuLayoutNode* node, int depth, const char* node_name, MenuLayoutMergeType merge_type, GString* str)
+{
+ const char* merge_type_str;
+
+ merge_type_str = NULL;
+
+ switch (merge_type)
+ {
+ case MENU_LAYOUT_MERGE_NONE:
+ merge_type_str = "none";
+ break;
+
+ case MENU_LAYOUT_MERGE_MENUS:
+ merge_type_str = "menus";
+ break;
+
+ case MENU_LAYOUT_MERGE_FILES:
+ merge_type_str = "files";
+ break;
+
+ case MENU_LAYOUT_MERGE_ALL:
+ merge_type_str = "all";
+ break;
+
+ default:
+ g_assert_not_reached();
+ break;
+ }
+
+ append_simple_with_attr(node, depth, node_name, "type", merge_type_str, str);
+}
+
+static void append_simple(MenuLayoutNode* node, int depth, const char* node_name, GString* str)
+{
+ append_simple_with_attr(node, depth, node_name, NULL, NULL, str);
+}
+
+static void append_start(MenuLayoutNode* node, int depth, const char* node_name, GString* str)
+{
+ append_spaces(str, depth);
+
+ g_string_append_printf(str, "<%s>\n", node_name);
+}
+
+static void append_end(MenuLayoutNode* node, int depth, const char* node_name, GString* str)
+{
+ append_spaces(str, depth);
+
+ g_string_append_printf(str, "</%s>\n", node_name);
+}
+
+static void append_container(MenuLayoutNode* node, gboolean onelevel, int depth, const char* node_name, GString* str)
+{
+ append_start(node, depth, node_name, str);
+
+ if (!onelevel)
+ {
+ append_children(node, depth + 2, str);
+ append_end(node, depth, node_name, str);
+ }
+}
+
+static void append_to_string(MenuLayoutNode* node, gboolean onelevel, int depth, GString* str)
+{
+ MenuLayoutValues layout_values;
+
+ switch (menu_layout_node_get_type(node))
+ {
+ case MENU_LAYOUT_NODE_ROOT:
+ if (!onelevel)
+ append_children(node, depth - 1, str); /* -1 to ignore depth of root */
+ else
+ append_start(node, depth - 1, "Root", str);
+ break;
+
+ case MENU_LAYOUT_NODE_PASSTHROUGH:
+ g_string_append(str, menu_layout_node_get_content(node));
+ g_string_append_c(str, '\n');
+ break;
+
+ case MENU_LAYOUT_NODE_MENU:
+ append_container(node, onelevel, depth, "Menu", str);
+ break;
+
+ case MENU_LAYOUT_NODE_APP_DIR:
+ append_simple(node, depth, "AppDir", str);
+ break;
+
+ case MENU_LAYOUT_NODE_DEFAULT_APP_DIRS:
+ append_simple(node, depth, "DefaultAppDirs", str);
+ break;
+
+ case MENU_LAYOUT_NODE_DIRECTORY_DIR:
+ append_simple(node, depth, "DirectoryDir", str);
+ break;
+
+ case MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS:
+ append_simple(node, depth, "DefaultDirectoryDirs", str);
+ break;
+
+ case MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS:
+ append_simple(node, depth, "DefaultMergeDirs", str);
+ break;
+
+ case MENU_LAYOUT_NODE_NAME:
+ append_simple(node, depth, "Name", str);
+ break;
+
+ case MENU_LAYOUT_NODE_DIRECTORY:
+ append_simple(node, depth, "Directory", str);
+ break;
+
+ case MENU_LAYOUT_NODE_ONLY_UNALLOCATED:
+ append_simple(node, depth, "OnlyUnallocated", str);
+ break;
+
+ case MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED:
+ append_simple(node, depth, "NotOnlyUnallocated", str);
+ break;
+
+ case MENU_LAYOUT_NODE_INCLUDE:
+ append_container(node, onelevel, depth, "Include", str);
+ break;
+
+ case MENU_LAYOUT_NODE_EXCLUDE:
+ append_container(node, onelevel, depth, "Exclude", str);
+ break;
+
+ case MENU_LAYOUT_NODE_FILENAME:
+ append_simple(node, depth, "Filename", str);
+ break;
+
+ case MENU_LAYOUT_NODE_CATEGORY:
+ append_simple(node, depth, "Category", str);
+ break;
+
+ case MENU_LAYOUT_NODE_ALL:
+ append_simple(node, depth, "All", str);
+ break;
+
+ case MENU_LAYOUT_NODE_AND:
+ append_container(node, onelevel, depth, "And", str);
+ break;
+
+ case MENU_LAYOUT_NODE_OR:
+ append_container(node, onelevel, depth, "Or", str);
+ break;
+
+ case MENU_LAYOUT_NODE_NOT:
+ append_container(node, onelevel, depth, "Not", str);
+ break;
+
+ case MENU_LAYOUT_NODE_MERGE_FILE:
+ {
+ MenuMergeFileType type;
+
+ type = menu_layout_node_merge_file_get_type(node);
+
+ append_simple_with_attr(node, depth, "MergeFile", "type", type == MENU_MERGE_FILE_TYPE_PARENT ? "parent" : "path", str);
+ break;
+ }
+
+ case MENU_LAYOUT_NODE_MERGE_DIR:
+ append_simple(node, depth, "MergeDir", str);
+ break;
+
+ case MENU_LAYOUT_NODE_LEGACY_DIR:
+ append_simple_with_attr(node, depth, "LegacyDir", "prefix", menu_layout_node_legacy_dir_get_prefix (node), str);
+ break;
+
+ case MENU_LAYOUT_NODE_KDE_LEGACY_DIRS:
+ append_simple(node, depth, "KDELegacyDirs", str);
+ break;
+
+ case MENU_LAYOUT_NODE_MOVE:
+ append_container(node, onelevel, depth, "Move", str);
+ break;
+
+ case MENU_LAYOUT_NODE_OLD:
+ append_simple(node, depth, "Old", str);
+ break;
+
+ case MENU_LAYOUT_NODE_NEW:
+ append_simple(node, depth, "New", str);
+ break;
+
+ case MENU_LAYOUT_NODE_DELETED:
+ append_simple(node, depth, "Deleted", str);
+ break;
+
+ case MENU_LAYOUT_NODE_NOT_DELETED:
+ append_simple(node, depth, "NotDeleted", str);
+ break;
+
+ case MENU_LAYOUT_NODE_LAYOUT:
+ append_container(node, onelevel, depth, "Layout", str);
+ break;
+
+ case MENU_LAYOUT_NODE_DEFAULT_LAYOUT:
+ menu_layout_node_default_layout_get_values(node, &layout_values);
+ append_layout(node, depth, "DefaultLayout", &layout_values, str);
+ break;
+
+ case MENU_LAYOUT_NODE_MENUNAME:
+ menu_layout_node_menuname_get_values(node, &layout_values);
+ append_layout(node, depth, "MenuName", &layout_values, str);
+ break;
+
+ case MENU_LAYOUT_NODE_SEPARATOR:
+ append_simple(node, depth, "Name", str);
+ break;
+
+ case MENU_LAYOUT_NODE_MERGE:
+ append_merge(node, depth, "Merge", menu_layout_node_merge_get_type(node), str);
+ break;
+
+ default:
+ g_assert_not_reached();
+ break;
+ }
+}
+
+void menu_debug_print_layout(MenuLayoutNode* node, gboolean onelevel)
+{
+ if (menu_verbose_enabled())
+ {
+ GString* str = g_string_new(NULL);
+ append_to_string(node, onelevel, 0, str);
+
+ utf8_fputs(str->str, stderr);
+ fflush(stderr);
+
+ g_string_free(str, TRUE);
+ }
+}
+
+#endif /* G_ENABLE_DEBUG */
diff --git a/libmenu/menu-util.h b/libmenu/menu-util.h
new file mode 100644
index 0000000..035f0e9
--- /dev/null
+++ b/libmenu/menu-util.h
@@ -0,0 +1,59 @@
+/* Random utility functions for menu code */
+
+/*
+ * Copyright (C) 2003 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __MENU_UTIL_H__
+#define __MENU_UTIL_H__
+
+#include <glib.h>
+
+#include "menu-layout.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#ifdef G_ENABLE_DEBUG
+
+ void menu_verbose(const char* format, ...) G_GNUC_PRINTF(1, 2);
+
+ void menu_debug_print_layout(MenuLayoutNode* node, gboolean onelevel);
+
+#else /* !defined(G_ENABLE_DEBUG) */
+
+ #ifdef G_HAVE_ISO_VARARGS
+ #define menu_verbose(...)
+ #elif defined(G_HAVE_GNUC_VARARGS)
+ #define menu_verbose(format...)
+ #else
+ #error "Cannot disable verbose mode due to lack of varargs macros"
+ #endif
+
+ #define menu_debug_print_layout(n, o)
+
+#endif /* G_ENABLE_DEBUG */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MENU_UTIL_H__ */