diff options
author | Perberos <[email protected]> | 2011-12-01 22:29:22 -0300 |
---|---|---|
committer | Perberos <[email protected]> | 2011-12-01 22:29:22 -0300 |
commit | 17e291fd05d66fe232e2f409d54331c495b2fbd0 (patch) | |
tree | d3f9b3bc149dedc9d192317e930d438c5e9ba21a /libmenu | |
download | mate-menus-17e291fd05d66fe232e2f409d54331c495b2fbd0.tar.bz2 mate-menus-17e291fd05d66fe232e2f409d54331c495b2fbd0.tar.xz |
moving from https://github.com/perberos/mate-desktop-environment
Diffstat (limited to 'libmenu')
-rw-r--r-- | libmenu/Makefile.am | 78 | ||||
-rw-r--r-- | libmenu/canonicalize.c | 326 | ||||
-rw-r--r-- | libmenu/canonicalize.h | 38 | ||||
-rw-r--r-- | libmenu/desktop-entries.c | 816 | ||||
-rw-r--r-- | libmenu/desktop-entries.h | 90 | ||||
-rw-r--r-- | libmenu/entry-directories.c | 1105 | ||||
-rw-r--r-- | libmenu/entry-directories.h | 67 | ||||
-rw-r--r-- | libmenu/libmate-menu-uninstalled.pc.in | 11 | ||||
-rw-r--r-- | libmenu/libmate-menu.pc.in | 11 | ||||
-rw-r--r-- | libmenu/matemenu-tree.c | 4520 | ||||
-rw-r--r-- | libmenu/matemenu-tree.h | 135 | ||||
-rw-r--r-- | libmenu/menu-layout.c | 2359 | ||||
-rw-r--r-- | libmenu/menu-layout.h | 161 | ||||
-rw-r--r-- | libmenu/menu-monitor.c | 440 | ||||
-rw-r--r-- | libmenu/menu-monitor.h | 70 | ||||
-rw-r--r-- | libmenu/menu-util.c | 436 | ||||
-rw-r--r-- | libmenu/menu-util.h | 59 |
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__ */ |