[Xfce4-commits] [apps/xfce4-screensaver] 325/425: Move external MATE dependencies in to begin port

noreply at xfce.org noreply at xfce.org
Mon Oct 15 01:52:52 CEST 2018


This is an automated email from the git hooks/post-receive script.

b   l   u   e   s   a   b   r   e       p   u   s   h   e   d       a       c   o   m   m   i   t       t   o       b   r   a   n   c   h       m   a   s   t   e   r   
   in repository apps/xfce4-screensaver.

commit b43a21a7c112db83b19f19afa3b82341b635e467
Author: Sean Davis <smd.seandavis at gmail.com>
Date:   Sun Sep 30 16:53:42 2018 -0400

    Move external MATE dependencies in to begin port
---
 NEWS                               |    4 +-
 configure.ac                       |   23 +-
 src/Makefile.am                    |  126 +
 src/canonicalize.c                 |  330 +++
 src/canonicalize.h                 |   38 +
 src/desktop-entries.c              |  816 +++++++
 src/desktop-entries.h              |   90 +
 src/edid.h                         |  184 ++
 src/entry-directories.c            | 1105 +++++++++
 src/entry-directories.h            |   67 +
 src/gs-fade.c                      |   30 +-
 src/gs-lock-plug.c                 |   14 +-
 src/gs-manager.c                   |   16 +-
 src/gs-theme-manager.c             |   58 +-
 src/mate-screensaver-preferences.c |    5 +-
 src/menu-layout.c                  | 2359 +++++++++++++++++++
 src/menu-layout.h                  |  161 ++
 src/menu-monitor.c                 |  431 ++++
 src/menu-monitor.h                 |   70 +
 src/menu-util.c                    |  436 ++++
 src/menu-util.h                    |   59 +
 src/private.h                      |   36 +
 src/xfce-bg-crossfade.c            |  753 ++++++
 src/xfce-bg-crossfade.h            |   76 +
 src/xfce-bg.c                      | 3262 ++++++++++++++++++++++++++
 src/xfce-bg.h                      |  168 ++
 src/xfce-desktop-thumbnail.c       | 1548 ++++++++++++
 src/xfce-desktop-thumbnail.h       |   99 +
 src/xfce-desktop-utils.c           |  460 ++++
 src/xfce-desktop-utils.h           |   54 +
 src/xfce-rr-config.c               | 1978 ++++++++++++++++
 src/xfce-rr-config.h               |  147 ++
 src/xfce-rr-labeler.c              |  550 +++++
 src/xfce-rr-labeler.h              |   61 +
 src/xfce-rr-output-info.c          |  254 ++
 src/xfce-rr-private.h              |   84 +
 src/xfce-rr.c                      | 2164 +++++++++++++++++
 src/xfce-rr.h                      |  196 ++
 src/xfcekbd-config-private.h       |   80 +
 src/xfcekbd-desktop-config.c       |  312 +++
 src/xfcekbd-desktop-config.h       |   91 +
 src/xfcekbd-indicator-config.c     |  341 +++
 src/xfcekbd-indicator-config.h     |   90 +
 src/xfcekbd-indicator-marshal.list |    1 +
 src/xfcekbd-indicator.c            |  918 ++++++++
 src/xfcekbd-indicator.h            |   80 +
 src/xfcekbd-keyboard-config.c      |  796 +++++++
 src/xfcekbd-keyboard-config.h      |  123 +
 src/xfcekbd-util.c                 |  132 ++
 src/xfcekbd-util.h                 |   36 +
 src/xfcemenu-tree.c                | 4556 ++++++++++++++++++++++++++++++++++++
 src/xfcemenu-tree.h                |  135 ++
 52 files changed, 25923 insertions(+), 80 deletions(-)

diff --git a/NEWS b/NEWS
index b97debd..20ba12c 100644
--- a/NEWS
+++ b/NEWS
@@ -34,7 +34,7 @@
   * NEWS: use consistent, project wide, markdown-like formatting to make
     generating release announcements easier
   * Build: require libmate-menu >= 1.10
-  * Build: require libmatekbd and libmate-desktop >= 1.17
+  * Build: require libxfcekbd and libmate-desktop >= 1.17
   * Move to GTK+3 (require GTK+ >= 3.14), drop GTK+2 code and --with-gtk
     build option
   * Lock screen: load user's background instead of system one
@@ -92,7 +92,7 @@
 
 ### mate-screensaver 1.10.0
 
-  * Update to api changes for MateRR* in mate-desktop (thanks monsta for testing)
+  * Update to api changes for XfceRR* in mate-desktop (thanks monsta for testing)
 
 ### mate-screensaver 1.8.0
 
diff --git a/configure.ac b/configure.ac
index 4b5ba3b..e342e58 100644
--- a/configure.ac
+++ b/configure.ac
@@ -47,9 +47,7 @@ DBUS_REQUIRED_VERSION=0.30
 GLIB_REQUIRED_VERSION=2.50.0
 GTK_REQUIRED_VERSION=3.22.0
 X11_REQUIRED_VERSION=1.0
-LIBMATE_MENU_REQUIRED_VERSION=1.10.0
-MATE_DESKTOP_REQUIRED_VERSION=1.17.0
-LIBMATEKBDUI_REQUIRED_VERSION=1.17.0
+LIBXKLAVIER_REQUIRED=5.2
 
 AC_CHECK_HEADERS(unistd.h)
 AC_CHECK_HEADERS(crypt.h sys/select.h)
@@ -65,24 +63,21 @@ PKG_CHECK_MODULES(MATE_SCREENSAVER,
         gtk+-3.0 >= $GTK_REQUIRED_VERSION
         dbus-glib-1 >= $DBUS_REQUIRED_VERSION
         gio-2.0 >= $GLIB_REQUIRED_VERSION
-        mate-desktop-2.0 >= $MATE_DESKTOP_REQUIRED_VERSION
-        libmate-menu >= $LIBMATE_MENU_REQUIRED_VERSION)
+        libxklavier >= $LIBXKLAVIER_REQUIRED)
 AC_SUBST(MATE_SCREENSAVER_CFLAGS)
 AC_SUBST(MATE_SCREENSAVER_LIBS)
 
 PKG_CHECK_MODULES(MATE_SCREENSAVER_DIALOG,
         gio-2.0 >= $GLIB_REQUIRED_VERSION
         gthread-2.0
-        gtk+-3.0 >= $GTK_REQUIRED_VERSION
-        mate-desktop-2.0 >= $MATE_DESKTOP_REQUIRED_VERSION)
+        gtk+-3.0 >= $GTK_REQUIRED_VERSION)
 AC_SUBST(MATE_SCREENSAVER_DIALOG_CFLAGS)
 AC_SUBST(MATE_SCREENSAVER_DIALOG_LIBS)
 
 PKG_CHECK_MODULES(MATE_SCREENSAVER_CAPPLET,
         gio-2.0 >= $GLIB_REQUIRED_VERSION
         gtk+-3.0 >= $GTK_REQUIRED_VERSION
-        mate-desktop-2.0 >= $MATE_DESKTOP_REQUIRED_VERSION
-        libmate-menu >= $LIBMATE_MENU_REQUIRED_VERSION)
+        libxklavier >= $LIBXKLAVIER_REQUIRED)
 AC_SUBST(MATE_SCREENSAVER_CAPPLET_CFLAGS)
 AC_SUBST(MATE_SCREENSAVER_CAPPLET_LIBS)
 
@@ -946,18 +941,13 @@ AC_SUBST(SYSTEMD_CFLAGS)
 AC_SUBST(SYSTEMD_LIBS)
 
 dnl ---------------------------------------------------------------------------
-dnl libmatekbd
+dnl libxfcekbd
 dnl ---------------------------------------------------------------------------
 
-have_libmatekbdui=no
+have_libxfcekbdui=no
 AC_ARG_WITH(kbd-layout-indicator,[  --without-kbd-layout-indicator         disable keyboard layout indicator],
             [with_kbd_layout_indicator="$withval"],[with_kbd_layout_indicator=yes])
 if test x$with_kbd_layout_indicator != xno; then
-  PKG_CHECK_MODULES(LIBMATEKBDUI, libmatekbdui >= $LIBMATEKBDUI_REQUIRED_VERSION, have_libmatekbdui=yes, have_libmatekbdui=no)
-fi
-if test "x$have_libmatekbdui" = "xyes"; then
-  AC_SUBST(LIBMATEKBDUI_CFLAGS)
-  AC_SUBST(LIBMATEKBDUI_LIBS)
   AC_DEFINE(WITH_KBD_LAYOUT_INDICATOR, 1, [Define if keyboard layout indicator should be built])
 fi
 
@@ -1086,6 +1076,7 @@ fi
 AC_SUBST(DEBUG_CFLAGS)
 
 # Flags
+CFLAGS="$CFLAGS -lm"
 
 AC_SUBST(CFLAGS)
 AC_SUBST(CPPFLAGS)
diff --git a/src/Makefile.am b/src/Makefile.am
index 51e09bf..65a0a13 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -31,6 +31,7 @@ AM_CPPFLAGS =							\
 	$(DBUS_CFLAGS)						\
 	$(LIBMATEKBDUI_CFLAGS)					\
 	$(LIBNOTIFY_CFLAGS)					\
+	$(LIBXKLAVIER_CFLAGS)				\
 	$(SYSTEMD_CFLAGS)					\
 	$(NULL)
 
@@ -76,6 +77,8 @@ test_fade_SOURCES = 			\
 	gs-fade.h	 		\
 	gs-debug.c			\
 	gs-debug.h			\
+	xfce-rr.c			\
+	xfce-rr.h			\
 	$(NULL)
 
 test_fade_LDADD =			\
@@ -141,6 +144,31 @@ mate_screensaver_dialog_SOURCES = 	\
 	setuid.h			\
 	subprocs.c			\
 	subprocs.h			\
+	xfce-desktop-utils.c		\
+	xfce-desktop-utils.h		\
+	xfcekbd-indicator.c		\
+	xfcekbd-indicator.h		\
+	xfcekbd-indicator-config.c		\
+	xfcekbd-indicator-config.h		\
+	xfcekbd-config-private.h		\
+	xfcekbd-desktop-config.c \
+	xfcekbd-desktop-config.h \
+	xfcekbd-keyboard-config.c \
+	xfcekbd-keyboard-config.h \
+	xfcekbd-util.c \
+	xfcekbd-util.h \
+	desktop-entries.c \
+	desktop-entries.h \
+	menu-layout.c \
+	menu-layout.h \
+	menu-util.c \
+	menu-util.h \
+	menu-monitor.c \
+	menu-monitor.h \
+	canonicalize.c \
+	canonicalize.h \
+	entry-directories.c \
+	entry-directories.h \
 	$(AUTH_SOURCES)			\
 	$(NULL)
 
@@ -149,10 +177,36 @@ EXTRA_mate_screensaver_dialog_SOURCES = \
 	gs-auth-helper.c	\
 	gs-auth-pwent.c		\
 	gs-auth-bsdauth.c	\
+	xfce-desktop-utils.c		\
+	xfce-desktop-utils.h		\
+	xfcekbd-indicator.c		\
+	xfcekbd-indicator.h		\
+	xfcekbd-indicator-config.c		\
+	xfcekbd-indicator-config.h		\
+	xfcekbd-config-private.h		\
+	xfcekbd-desktop-config.c \
+	xfcekbd-desktop-config.h \
+	xfcekbd-keyboard-config.c \
+	xfcekbd-keyboard-config.h \
+	xfcekbd-util.c \
+	xfcekbd-util.h \
+	desktop-entries.c \
+	desktop-entries.h \
+	menu-layout.c \
+	menu-layout.h \
+	menu-util.c \
+	menu-util.h \
+	menu-monitor.c \
+	menu-monitor.h \
+	canonicalize.c \
+	canonicalize.h \
+	entry-directories.c \
+	entry-directories.h \
 	$(NULL)
 
 mate_screensaver_dialog_LDADD =	\
 	$(MATE_SCREENSAVER_DIALOG_LIBS)\
+	$(MATE_SCREENSAVER_LIBS)\
 	$(SAVER_LIBS)			\
 	$(AUTH_LIBS)			\
 	$(LIBMATEKBDUI_LIBS)		\
@@ -162,6 +216,7 @@ mate_screensaver_dialog_LDADD =	\
 BUILT_SOURCES = 		\
 	gs-marshal.c 		\
 	gs-marshal.h		\
+	xfcekbd-indicator-marshal.h \
 	$(NULL)
 
 gs-marshal.c: gs-marshal.list
@@ -198,6 +253,42 @@ mate_screensaver_SOURCES =	\
 	gs-grab.h		\
 	gs-fade.c		\
 	gs-fade.h		\
+	xfce-rr.c		\
+	xfce-rr.h		\
+	xfce-bg.c		\
+	xfce-bg.h		\
+	xfce-bg-crossfade.c		\
+	xfce-bg-crossfade.h		\
+	xfce-desktop-thumbnail.c		\
+	xfce-desktop-thumbnail.h		\
+	xfce-desktop-utils.c		\
+	xfce-desktop-utils.h		\
+	xfcekbd-indicator.c		\
+	xfcekbd-indicator.h		\
+	xfcekbd-indicator-config.c		\
+	xfcekbd-indicator-config.h		\
+	xfcekbd-config-private.h		\
+	xfcekbd-desktop-config.c \
+	xfcekbd-desktop-config.h \
+	xfcekbd-keyboard-config.c \
+	xfcekbd-keyboard-config.h \
+	xfcekbd-util.c \
+	xfcekbd-util.h \
+	xfcemenu-tree.c \
+	xfcemenu-tree.h \
+	desktop-entries.c \
+	desktop-entries.h \
+	menu-layout.c \
+	menu-layout.h \
+	menu-util.c \
+	menu-util.h \
+	menu-monitor.c \
+	menu-monitor.h \
+	canonicalize.c \
+	canonicalize.h \
+	entry-directories.c \
+	entry-directories.h \
+	private.h		\
 	$(BUILT_SOURCES)	\
 	$(NULL)
 
@@ -232,6 +323,33 @@ mate_screensaver_preferences_SOURCES =	\
 	gs-debug.h			\
 	subprocs.c			\
 	subprocs.h			\
+	xfce-desktop-utils.c		\
+	xfce-desktop-utils.h		\
+	xfcekbd-indicator.c		\
+	xfcekbd-indicator.h		\
+	xfcekbd-indicator-config.c		\
+	xfcekbd-indicator-config.h		\
+	xfcekbd-config-private.h		\
+	xfcekbd-desktop-config.c \
+	xfcekbd-desktop-config.h \
+	xfcekbd-keyboard-config.c \
+	xfcekbd-keyboard-config.h \
+	xfcekbd-util.c \
+	xfcekbd-util.h \
+	xfcemenu-tree.c \
+	xfcemenu-tree.h \
+	canonicalize.c \
+	canonicalize.h \
+	entry-directories.c \
+	entry-directories.h \
+	menu-util.c \
+	menu-util.h \
+	menu-layout.c \
+	menu-layout.h \
+	menu-monitor.c \
+	menu-monitor.h \
+	desktop-entries.c \
+	desktop-entries.h \
 	$(NULL)
 
 mate_screensaver_preferences_LDADD =	\
@@ -244,6 +362,14 @@ EXTRA_DIST =				\
 	mate-screensaver.desktop.in	\
 	$(NULL)
 
+GLIB_GENMARSHAL = $(shell pkg-config --variable=glib_genmarshal glib-2.0)
+
+xfcekbd-indicator-marshal.h: xfcekbd-indicator-marshal.list
+	$(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=xfcekbd_indicator $(srcdir)/xfcekbd-indicator-marshal.list --header > $@
+
+xfcekbd-indicator-marshal.c: xfcekbd-indicator-marshal.h
+	$(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=xfcekbd_indicator $(srcdir)/xfcekbd-indicator-marshal.list --body > $@
+
 CLEANFILES = 				\
 	$(desktop_DATA) 		\
 	mate-screensaver.desktop.in	\
diff --git a/src/canonicalize.c b/src/canonicalize.c
new file mode 100644
index 0000000..1a29ae9
--- /dev/null
+++ b/src/canonicalize.c
@@ -0,0 +1,330 @@
+/* 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 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>
+
+#ifndef MAXSYMLINKS
+#define MAXSYMLINKS sysconf(_SC_SYMLOOP_MAX)
+#endif
+
+/* 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/src/canonicalize.h b/src/canonicalize.h
new file mode 100644
index 0000000..dcc6f6e
--- /dev/null
+++ b/src/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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 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/src/desktop-entries.c b/src/desktop-entries.c
new file mode 100644
index 0000000..c9fcc0f
--- /dev/null
+++ b/src/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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, 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/src/desktop-entries.h b/src/desktop-entries.h
new file mode 100644
index 0000000..a67cc9f
--- /dev/null
+++ b/src/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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, 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/src/edid.h b/src/edid.h
new file mode 100644
index 0000000..c7f7e20
--- /dev/null
+++ b/src/edid.h
@@ -0,0 +1,184 @@
+/* edid.h
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Mate 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Mate Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Soren Sandmann <sandmann at redhat.com>
+ */
+
+#ifndef EDID_H
+#define EDID_H
+
+typedef unsigned char uchar;
+typedef struct MonitorInfo MonitorInfo;
+typedef struct Timing Timing;
+typedef struct DetailedTiming DetailedTiming;
+
+typedef enum {
+	UNDEFINED,
+	DVI,
+	HDMI_A,
+	HDMI_B,
+	MDDI,
+	DISPLAY_PORT
+} Interface;
+
+typedef enum {
+	UNDEFINED_COLOR,
+	MONOCHROME,
+	RGB,
+	OTHER_COLOR
+} ColorType;
+
+typedef enum {
+	NO_STEREO,
+	FIELD_RIGHT,
+	FIELD_LEFT,
+	TWO_WAY_RIGHT_ON_EVEN,
+	TWO_WAY_LEFT_ON_EVEN,
+	FOUR_WAY_INTERLEAVED,
+	SIDE_BY_SIDE
+} StereoType;
+
+struct Timing {
+	int width;
+	int height;
+	int frequency;
+};
+
+struct DetailedTiming {
+	int pixel_clock;
+	int h_addr;
+	int h_blank;
+	int h_sync;
+    int h_front_porch;
+	int v_addr;
+	int v_blank;
+	int v_sync;
+	int v_front_porch;
+	int width_mm;
+	int height_mm;
+	int right_border;
+	int top_border;
+	int interlaced;
+	StereoType stereo;
+
+	int digital_sync;
+
+	union {
+		struct {
+			int bipolar;
+			int serrations;
+			int sync_on_green;
+		} analog;
+
+		struct {
+			int composite;
+			int serrations;
+			int negative_vsync;
+			int negative_hsync;
+		} digital;
+	} connector;
+};
+
+struct MonitorInfo {
+	int checksum;
+	char manufacturer_code[4];
+	int product_code;
+	unsigned int serial_number;
+
+	int production_week;	/* -1 if not specified */
+	int production_year;	/* -1 if not specified */
+	int model_year;		/* -1 if not specified */
+
+	int major_version;
+	int minor_version;
+
+	int is_digital;
+
+	union {
+		struct {
+			int bits_per_primary;
+			Interface interface;
+			int rgb444;
+			int ycrcb444;
+			int ycrcb422;
+		} digital;
+
+		struct {
+			double video_signal_level;
+			double sync_signal_level;
+			double total_signal_level;
+
+			int blank_to_black;
+
+			int separate_hv_sync;
+			int composite_sync_on_h;
+			int composite_sync_on_green;
+			int serration_on_vsync;
+			ColorType color_type;
+		} analog;
+	} connector;
+
+	int width_mm;		/* -1 if not specified */
+	int height_mm;		/* -1 if not specified */
+	double aspect_ratio;		/* -1.0 if not specififed */
+
+	double gamma;			/* -1.0 if not specified */
+
+	int standby;
+	int suspend;
+	int active_off;
+
+	int srgb_is_standard;
+	int preferred_timing_includes_native;
+	int continuous_frequency;
+
+	double red_x;
+	double red_y;
+	double green_x;
+	double green_y;
+	double blue_x;
+	double blue_y;
+	double white_x;
+	double white_y;
+
+	Timing established[24];	/* Terminated by 0x0x0 */
+	Timing standard[8];
+
+	int n_detailed_timings;
+	DetailedTiming detailed_timings[4];
+	/* If monitor has a preferred
+	 * mode, it is the first one
+	 * (whether it has, is
+	 * determined by the
+	 * preferred_timing_includes
+	 * bit.
+	 */
+
+	/* Optional product description */
+	char dsc_serial_number[14];
+	char dsc_product_name[14];
+	char dsc_string[14]; /* Unspecified ASCII data */
+};
+
+MonitorInfo* decode_edid(const uchar* data);
+char* make_display_name(const MonitorInfo* info);
+
+#endif /* !EDID_H */
diff --git a/src/entry-directories.c b/src/entry-directories.c
new file mode 100644
index 0000000..2b59b37
--- /dev/null
+++ b/src/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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, 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/src/entry-directories.h b/src/entry-directories.h
new file mode 100644
index 0000000..5b9dfae
--- /dev/null
+++ b/src/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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, 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/src/gs-fade.c b/src/gs-fade.c
index f949ee5..eff27e3 100644
--- a/src/gs-fade.c
+++ b/src/gs-fade.c
@@ -41,9 +41,9 @@
 #include "gs-fade.h"
 #include "gs-debug.h"
 
-#define MATE_DESKTOP_USE_UNSTABLE_API
 
-#include "libmate-desktop/mate-rr.h"
+
+#include "xfce-rr.h"
 
 /* XFree86 4.x+ Gamma fading */
 
@@ -77,7 +77,7 @@ struct GSFadeScreenPrivate
 	/* one per crtc in randr mode */
 	struct GSGammaInfo *info;
 	/* one per screen in theory */
-	MateRRScreen      *rrscreen;
+	XfceRRScreen      *rrscreen;
 #ifdef HAVE_XF86VMODE_GAMMA
 	/* one per screen also */
 	XF86VidModeGamma    vmg;
@@ -468,8 +468,8 @@ fade_none:
 static gboolean xrandr_fade_setup (GSFade *fade)
 {
 	struct GSFadeScreenPrivate *screen_priv;
-	MateRRCrtc *crtc;
-	MateRRCrtc **crtcs;
+	XfceRRCrtc *crtc;
+	XfceRRCrtc **crtcs;
 	int crtc_count = 0;
 	struct GSGammaInfo *info;
 	gboolean res;
@@ -480,9 +480,9 @@ static gboolean xrandr_fade_setup (GSFade *fade)
 		return TRUE;
 
 	/* refresh the screen info */
-	mate_rr_screen_refresh (screen_priv->rrscreen, NULL);
+	xfce_rr_screen_refresh (screen_priv->rrscreen, NULL);
 
-	crtcs = mate_rr_screen_list_crtcs (screen_priv->rrscreen);
+	crtcs = xfce_rr_screen_list_crtcs (screen_priv->rrscreen);
 	while (*crtcs)
 	{
 		crtc_count++;
@@ -493,7 +493,7 @@ static gboolean xrandr_fade_setup (GSFade *fade)
 	screen_priv->num_ramps = crtc_count;
 
 	crtc_count = 0;
-	crtcs = mate_rr_screen_list_crtcs (screen_priv->rrscreen);
+	crtcs = xfce_rr_screen_list_crtcs (screen_priv->rrscreen);
 	while (*crtcs)
 	{
 		crtc = *crtcs;
@@ -501,7 +501,7 @@ static gboolean xrandr_fade_setup (GSFade *fade)
 		info = &screen_priv->info[crtc_count];
 
 		/* if no mode ignore crtc */
-		if (!mate_rr_crtc_get_current_mode (crtc))
+		if (!xfce_rr_crtc_get_current_mode (crtc))
 		{
 			info->size = 0;
 			info->r = NULL;
@@ -510,7 +510,7 @@ static gboolean xrandr_fade_setup (GSFade *fade)
 		}
 		else
 		{
-			res = mate_rr_crtc_get_gamma (crtc, &info->size,
+			res = xfce_rr_crtc_get_gamma (crtc, &info->size,
 			                              &info->r, &info->g,
 			                              &info->b);
 			if (res == FALSE)
@@ -525,7 +525,7 @@ fail:
 	return FALSE;
 }
 
-static void xrandr_crtc_whack_gamma (MateRRCrtc *crtc,
+static void xrandr_crtc_whack_gamma (XfceRRCrtc *crtc,
                                      struct GSGammaInfo *gamma_info,
                                      float            ratio)
 {
@@ -555,7 +555,7 @@ static void xrandr_crtc_whack_gamma (MateRRCrtc *crtc,
 		b[i] = gamma_info->b[i] * ratio;
 	}
 
-	mate_rr_crtc_set_gamma (crtc, gamma_info->size,
+	xfce_rr_crtc_set_gamma (crtc, gamma_info->size,
 	                        r, g, b);
 	g_free (r);
 	g_free (g);
@@ -567,7 +567,7 @@ static gboolean xrandr_fade_set_alpha_gamma (GSFade *fade,
 {
 	struct GSFadeScreenPrivate *screen_priv;
 	struct GSGammaInfo *info;
-	MateRRCrtc **crtcs;
+	XfceRRCrtc **crtcs;
 	int i;
 
 	screen_priv = &fade->priv->screen_priv;
@@ -575,7 +575,7 @@ static gboolean xrandr_fade_set_alpha_gamma (GSFade *fade,
 	if (!screen_priv->info)
 		return FALSE;
 
-	crtcs = mate_rr_screen_list_crtcs (screen_priv->rrscreen);
+	crtcs = xfce_rr_screen_list_crtcs (screen_priv->rrscreen);
 	i = 0;
 
 	while (*crtcs)
@@ -597,7 +597,7 @@ check_randr_extension (GSFade *fade)
 
 	screen_priv = &fade->priv->screen_priv;
 
-	screen_priv->rrscreen = mate_rr_screen_new (screen,
+	screen_priv->rrscreen = xfce_rr_screen_new (screen,
 	                        NULL);
 	if (!screen_priv->rrscreen)
 	{
diff --git a/src/gs-lock-plug.c b/src/gs-lock-plug.c
index e4692e4..c26db9d 100644
--- a/src/gs-lock-plug.c
+++ b/src/gs-lock-plug.c
@@ -39,11 +39,11 @@
 #include <gtk/gtkx.h>
 #include <gio/gio.h>
 
-#define MATE_DESKTOP_USE_UNSTABLE_API
-#include <libmate-desktop/mate-desktop-utils.h>
+
+#include "xfce-desktop-utils.h"
 
 #ifdef WITH_KBD_LAYOUT_INDICATOR
-#include <libmatekbd/matekbd-indicator.h>
+#include "xfcekbd-indicator.h"
 #endif
 
 #ifdef WITH_LIBNOTIFY
@@ -215,7 +215,7 @@ do_user_switch (GSLockPlug *plug)
 								   MDM_FLEXISERVER_ARGS);
 
 		error = NULL;
-		res = mate_gdk_spawn_command_line_on_screen (gdk_screen_get_default (),
+		res = xfce_gdk_spawn_command_line_on_screen (gdk_screen_get_default (),
 												command,
 												&error);
 
@@ -235,7 +235,7 @@ do_user_switch (GSLockPlug *plug)
 								   GDM_FLEXISERVER_ARGS);
 
 		error = NULL;
-		res = mate_gdk_spawn_command_line_on_screen (gdk_screen_get_default (),
+		res = xfce_gdk_spawn_command_line_on_screen (gdk_screen_get_default (),
 												command,
 												&error);
 
@@ -2148,8 +2148,8 @@ gs_lock_plug_init (GSLockPlug *plug)
 		{
 			GtkWidget *layout_indicator;
 
-			layout_indicator = matekbd_indicator_new ();
-			matekbd_indicator_set_parent_tooltips (MATEKBD_INDICATOR (layout_indicator), TRUE);
+			layout_indicator = xfcekbd_indicator_new ();
+			xfcekbd_indicator_set_parent_tooltips (XFCEKBD_INDICATOR (layout_indicator), TRUE);
 			gtk_box_pack_start (GTK_BOX (plug->priv->auth_prompt_kbd_layout_indicator),
 			                    layout_indicator,
 			                    FALSE,
diff --git a/src/gs-manager.c b/src/gs-manager.c
index fe2e4fe..6d9aa5a 100644
--- a/src/gs-manager.c
+++ b/src/gs-manager.c
@@ -28,8 +28,8 @@
 
 #include <gio/gio.h>
 
-#define MATE_DESKTOP_USE_UNSTABLE_API
-#include <libmate-desktop/mate-bg.h>
+
+#include "xfce-bg.h"
 
 #include "gs-prefs.h"        /* for GSSaverMode */
 
@@ -53,7 +53,7 @@ struct GSManagerPrivate
 	GHashTable  *jobs;
 
 	GSThemeManager *theme_manager;
-	MateBG        *bg;
+	XfceBG        *bg;
 
 	/* Policy */
 	glong        lock_timeout;
@@ -1032,7 +1032,7 @@ gs_manager_class_init (GSManagerClass *klass)
 }
 
 static void
-on_bg_changed (MateBG   *bg,
+on_bg_changed (XfceBG   *bg,
                GSManager *manager)
 {
 	gs_debug ("background changed");
@@ -1047,14 +1047,14 @@ gs_manager_init (GSManager *manager)
 	manager->priv->grab = gs_grab_new ();
 	manager->priv->theme_manager = gs_theme_manager_new ();
 
-	manager->priv->bg = mate_bg_new ();
+	manager->priv->bg = xfce_bg_new ();
 
 	g_signal_connect (manager->priv->bg,
 					  "changed",
 					  G_CALLBACK (on_bg_changed),
 					  manager);
 
-	mate_bg_load_from_preferences (manager->priv->bg);
+	xfce_bg_load_from_preferences (manager->priv->bg);
 }
 
 static void
@@ -1274,7 +1274,7 @@ apply_background_to_window (GSManager *manager,
 	int              height;
 	gint             scale;
 
-        mate_bg_load_from_preferences (manager->priv->bg);
+        xfce_bg_load_from_preferences (manager->priv->bg);
 
 	if (manager->priv->bg == NULL)
 	{
@@ -1288,7 +1288,7 @@ apply_background_to_window (GSManager *manager,
 	width = WidthOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale;
 	height = HeightOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale;
 	gs_debug ("Creating background w:%d h:%d", width, height);
-	surface = mate_bg_create_surface (manager->priv->bg,
+	surface = xfce_bg_create_surface (manager->priv->bg,
 	                                  gs_window_get_gdk_window (window),
 	                                  width,
 	                                  height,
diff --git a/src/gs-theme-manager.c b/src/gs-theme-manager.c
index 350e28c..42b3c5f 100644
--- a/src/gs-theme-manager.c
+++ b/src/gs-theme-manager.c
@@ -31,7 +31,7 @@
 #endif /* HAVE_UNISTD_H */
 
 #include <glib-object.h>
-#include <matemenu-tree.h>
+#include <xfcemenu-tree.h>
 
 #include "gs-theme-manager.h"
 #include "gs-debug.h"
@@ -52,7 +52,7 @@ struct _GSThemeInfo
 
 struct GSThemeManagerPrivate
 {
-	MateMenuTree *menu_tree;
+	XfceMenuTree *menu_tree;
 };
 
 G_DEFINE_TYPE (GSThemeManager, gs_theme_manager, G_TYPE_OBJECT)
@@ -232,7 +232,7 @@ gs_theme_info_get_exec (GSThemeInfo *info)
 }
 
 static GSThemeInfo *
-gs_theme_info_new_from_matemenu_tree_entry (MateMenuTreeEntry *entry)
+gs_theme_info_new_from_xfcemenu_tree_entry (XfceMenuTreeEntry *entry)
 {
 	GSThemeInfo *info;
 	const char     *str;
@@ -241,11 +241,11 @@ gs_theme_info_new_from_matemenu_tree_entry (MateMenuTreeEntry *entry)
 	info = g_new0 (GSThemeInfo, 1);
 
 	info->refcount = 1;
-	info->name     = g_strdup (matemenu_tree_entry_get_name (entry));
-	info->exec     = g_strdup (matemenu_tree_entry_get_exec (entry));
+	info->name     = g_strdup (xfcemenu_tree_entry_get_name (entry));
+	info->exec     = g_strdup (xfcemenu_tree_entry_get_exec (entry));
 
 	/* remove the .desktop suffix */
-	str = matemenu_tree_entry_get_desktop_file_id (entry);
+	str = xfcemenu_tree_entry_get_desktop_file_id (entry);
 	pos = g_strrstr (str, ".desktop");
 	if (pos)
 	{
@@ -260,44 +260,44 @@ gs_theme_info_new_from_matemenu_tree_entry (MateMenuTreeEntry *entry)
 }
 
 static GSThemeInfo *
-find_info_for_id (MateMenuTree  *tree,
+find_info_for_id (XfceMenuTree  *tree,
                   const char *id)
 {
 	GSThemeInfo     *info;
-	MateMenuTreeDirectory *root;
+	XfceMenuTreeDirectory *root;
 	GSList             *items;
 	GSList             *l;
 
-	root = matemenu_tree_get_root_directory (tree);
+	root = xfcemenu_tree_get_root_directory (tree);
 	if (root == NULL)
 	{
 		return NULL;
 	}
 
-	items = matemenu_tree_directory_get_contents (root);
+	items = xfcemenu_tree_directory_get_contents (root);
 
 	info = NULL;
 
 	for (l = items; l; l = l->next)
 	{
 		if (info == NULL
-		        && matemenu_tree_item_get_type (l->data) == MATEMENU_TREE_ITEM_ENTRY)
+		        && xfcemenu_tree_item_get_type (l->data) == MATEMENU_TREE_ITEM_ENTRY)
 		{
-			MateMenuTreeEntry *entry = l->data;
+			XfceMenuTreeEntry *entry = l->data;
 			const char     *file_id;
 
-			file_id = matemenu_tree_entry_get_desktop_file_id (entry);
+			file_id = xfcemenu_tree_entry_get_desktop_file_id (entry);
 			if (file_id && id && strcmp (file_id, id) == 0)
 			{
-				info = gs_theme_info_new_from_matemenu_tree_entry (entry);
+				info = gs_theme_info_new_from_xfcemenu_tree_entry (entry);
 			}
 		}
 
-		matemenu_tree_item_unref (l->data);
+		xfcemenu_tree_item_unref (l->data);
 	}
 
 	g_slist_free (items);
-	matemenu_tree_item_unref (root);
+	xfcemenu_tree_item_unref (root);
 
 	return info;
 }
@@ -321,29 +321,29 @@ gs_theme_manager_lookup_theme_info (GSThemeManager *theme_manager,
 
 static void
 theme_prepend_entry (GSList         **parent_list,
-                     MateMenuTreeEntry  *entry,
+                     XfceMenuTreeEntry  *entry,
                      const char      *filename)
 {
 	GSThemeInfo *info;
 
-	info = gs_theme_info_new_from_matemenu_tree_entry (entry);
+	info = gs_theme_info_new_from_xfcemenu_tree_entry (entry);
 
 	*parent_list = g_slist_prepend (*parent_list, info);
 }
 
 static void
 make_theme_list (GSList             **parent_list,
-                 MateMenuTreeDirectory  *directory,
+                 XfceMenuTreeDirectory  *directory,
                  const char          *filename)
 {
 	GSList *items;
 	GSList *l;
 
-	items = matemenu_tree_directory_get_contents (directory);
+	items = xfcemenu_tree_directory_get_contents (directory);
 
 	for (l = items; l; l = l->next)
 	{
-		switch (matemenu_tree_item_get_type (l->data))
+		switch (xfcemenu_tree_item_get_type (l->data))
 		{
 
 		case MATEMENU_TREE_ITEM_ENTRY:
@@ -356,7 +356,7 @@ make_theme_list (GSList             **parent_list,
 			break;
 		}
 
-		matemenu_tree_item_unref (l->data);
+		xfcemenu_tree_item_unref (l->data);
 	}
 
 	g_slist_free (items);
@@ -368,16 +368,16 @@ GSList *
 gs_theme_manager_get_info_list (GSThemeManager *theme_manager)
 {
 	GSList             *l = NULL;
-	MateMenuTreeDirectory *root;
+	XfceMenuTreeDirectory *root;
 
 	g_return_val_if_fail (GS_IS_THEME_MANAGER (theme_manager), NULL);
 
-	root = matemenu_tree_get_root_directory (theme_manager->priv->menu_tree);
+	root = xfcemenu_tree_get_root_directory (theme_manager->priv->menu_tree);
 
 	if (root != NULL)
 	{
 		make_theme_list (&l, root, "mate-screensavers.menu");
-		matemenu_tree_item_unref (root);
+		xfcemenu_tree_item_unref (root);
 	}
 
 	return l;
@@ -393,16 +393,16 @@ gs_theme_manager_class_init (GSThemeManagerClass *klass)
 	g_type_class_add_private (klass, sizeof (GSThemeManagerPrivate));
 }
 
-static MateMenuTree *
+static XfceMenuTree *
 get_themes_tree (void)
 {
-	MateMenuTree *themes_tree = NULL;
+	XfceMenuTree *themes_tree = NULL;
 
 	/* we only need to add the locations to the path once
 	   and since this is only run once we'll do it here */
 	add_known_engine_locations_to_path ();
 
-	themes_tree = matemenu_tree_lookup ("mate-screensavers.menu", MATEMENU_TREE_FLAGS_NONE);
+	themes_tree = xfcemenu_tree_lookup ("mate-screensavers.menu", MATEMENU_TREE_FLAGS_NONE);
 
 	return themes_tree;
 }
@@ -429,7 +429,7 @@ gs_theme_manager_finalize (GObject *object)
 
 	if (theme_manager->priv->menu_tree != NULL)
 	{
-		matemenu_tree_unref (theme_manager->priv->menu_tree);
+		xfcemenu_tree_unref (theme_manager->priv->menu_tree);
 	}
 
 	G_OBJECT_CLASS (gs_theme_manager_parent_class)->finalize (object);
diff --git a/src/mate-screensaver-preferences.c b/src/mate-screensaver-preferences.c
index f767f5c..bf62bdd 100644
--- a/src/mate-screensaver-preferences.c
+++ b/src/mate-screensaver-preferences.c
@@ -36,8 +36,7 @@
 
 #include <gio/gio.h>
 
-#define MATE_DESKTOP_USE_UNSTABLE_API
-#include <libmate-desktop/mate-desktop-utils.h>
+#include "xfce-desktop-utils.h"
 
 #include "gs-debug.h"
 
@@ -423,7 +422,7 @@ response_cb (GtkWidget *widget,
 
 		error = NULL;
 
-		res = mate_gdk_spawn_command_line_on_screen (gdk_screen_get_default (),
+		res = xfce_gdk_spawn_command_line_on_screen (gdk_screen_get_default (),
 		                                        GPM_COMMAND,
 		                                        &error);
 		if (! res)
diff --git a/src/menu-layout.c b/src/menu-layout.c
new file mode 100644
index 0000000..89e9752
--- /dev/null
+++ b/src/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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, 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/src/menu-layout.h b/src/menu-layout.h
new file mode 100644
index 0000000..5d3e81a
--- /dev/null
+++ b/src/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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, 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/src/menu-monitor.c b/src/menu-monitor.c
new file mode 100644
index 0000000..48215c5
--- /dev/null
+++ b/src/menu-monitor.c
@@ -0,0 +1,431 @@
+/*
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, 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)
+{
+  MenuMonitor     *retval;
+  GFile           *file;
+
+  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/src/menu-monitor.h b/src/menu-monitor.h
new file mode 100644
index 0000000..8180c68
--- /dev/null
+++ b/src/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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, 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/src/menu-util.c b/src/menu-util.c
new file mode 100644
index 0000000..9d92dc1
--- /dev/null
+++ b/src/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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, 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/src/menu-util.h b/src/menu-util.h
new file mode 100644
index 0000000..c8721e2
--- /dev/null
+++ b/src/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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, 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__ */
diff --git a/src/private.h b/src/private.h
new file mode 100644
index 0000000..baa4ba7
--- /dev/null
+++ b/src/private.h
@@ -0,0 +1,36 @@
+/* private.h: various private functions
+
+   Copyright 2009, Novell, Inc.
+
+   This file is part of the Mate Library.
+
+   The Mate Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Mate 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Mate Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+
+   Author: Vincent Untz <vuntz at gnome.org>
+*/
+
+#ifndef __MATE_DESKTOP_PRIVATE_H__
+#define __MATE_DESKTOP_PRIVATE_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void _mate_desktop_init_i18n (void);
+
+G_END_DECLS
+
+#endif
diff --git a/src/xfce-bg-crossfade.c b/src/xfce-bg-crossfade.c
new file mode 100644
index 0000000..34426c8
--- /dev/null
+++ b/src/xfce-bg-crossfade.c
@@ -0,0 +1,753 @@
+/* xfce-bg-crossfade.h - fade window background between two surfaces
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * Author: Ray Strode <rstrode at redhat.com>
+*/
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+
+#include <gio/gio.h>
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <gtk/gtk.h>
+
+#include <cairo.h>
+#include <cairo-xlib.h>
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+#include <xfce-bg.h>
+#include "xfce-bg-crossfade.h"
+
+struct _XfceBGCrossfadePrivate
+{
+	GdkWindow       *window;
+	GtkWidget       *widget;
+	int              width;
+	int              height;
+	cairo_surface_t *fading_surface;
+	cairo_surface_t *start_surface;
+	cairo_surface_t *end_surface;
+	gdouble          start_time;
+	gdouble          total_duration;
+	guint            timeout_id;
+	guint            is_first_frame : 1;
+};
+
+enum {
+	PROP_0,
+	PROP_WIDTH,
+	PROP_HEIGHT,
+};
+
+enum {
+	FINISHED,
+	NUMBER_OF_SIGNALS
+};
+
+static guint signals[NUMBER_OF_SIGNALS] = { 0 };
+
+G_DEFINE_TYPE (XfceBGCrossfade, xfce_bg_crossfade, G_TYPE_OBJECT)
+#define XFCE_BG_CROSSFADE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o),\
+			                   XFCE_TYPE_BG_CROSSFADE,\
+			                   XfceBGCrossfadePrivate))
+
+static void
+xfce_bg_crossfade_set_property (GObject      *object,
+				 guint         property_id,
+				 const GValue *value,
+				 GParamSpec   *pspec)
+{
+	XfceBGCrossfade *fade;
+
+	g_assert (XFCE_IS_BG_CROSSFADE (object));
+
+	fade = XFCE_BG_CROSSFADE (object);
+
+	switch (property_id)
+	{
+	case PROP_WIDTH:
+		fade->priv->width = g_value_get_int (value);
+		break;
+	case PROP_HEIGHT:
+		fade->priv->height = g_value_get_int (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+xfce_bg_crossfade_get_property (GObject    *object,
+			     guint       property_id,
+			     GValue     *value,
+			     GParamSpec *pspec)
+{
+	XfceBGCrossfade *fade;
+
+	g_assert (XFCE_IS_BG_CROSSFADE (object));
+
+	fade = XFCE_BG_CROSSFADE (object);
+
+	switch (property_id)
+	{
+	case PROP_WIDTH:
+		g_value_set_int (value, fade->priv->width);
+		break;
+	case PROP_HEIGHT:
+		g_value_set_int (value, fade->priv->height);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+xfce_bg_crossfade_finalize (GObject *object)
+{
+	XfceBGCrossfade *fade;
+
+	fade = XFCE_BG_CROSSFADE (object);
+
+	xfce_bg_crossfade_stop (fade);
+
+	if (fade->priv->fading_surface != NULL) {
+		cairo_surface_destroy (fade->priv->fading_surface);
+		fade->priv->fading_surface = NULL;
+	}
+
+	if (fade->priv->start_surface != NULL) {
+		cairo_surface_destroy (fade->priv->start_surface);
+		fade->priv->start_surface = NULL;
+	}
+
+	if (fade->priv->end_surface != NULL) {
+		cairo_surface_destroy (fade->priv->end_surface);
+		fade->priv->end_surface = NULL;
+	}
+}
+
+static void
+xfce_bg_crossfade_class_init (XfceBGCrossfadeClass *fade_class)
+{
+	GObjectClass *gobject_class;
+
+	gobject_class = G_OBJECT_CLASS (fade_class);
+
+	gobject_class->get_property = xfce_bg_crossfade_get_property;
+	gobject_class->set_property = xfce_bg_crossfade_set_property;
+	gobject_class->finalize = xfce_bg_crossfade_finalize;
+
+	/**
+	 * XfceBGCrossfade:width:
+	 *
+	 * When a crossfade is running, this is width of the fading
+	 * surface.
+	 */
+	g_object_class_install_property (gobject_class,
+					 PROP_WIDTH,
+					 g_param_spec_int ("width",
+						           "Window Width",
+							    "Width of window to fade",
+							    0, G_MAXINT, 0,
+							    G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	/**
+	 * XfceBGCrossfade:height:
+	 *
+	 * When a crossfade is running, this is height of the fading
+	 * surface.
+	 */
+	g_object_class_install_property (gobject_class,
+					 PROP_HEIGHT,
+					 g_param_spec_int ("height", "Window Height",
+						           "Height of window to fade on",
+							   0, G_MAXINT, 0,
+							   G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	/**
+	 * XfceBGCrossfade::finished:
+	 * @fade: the #XfceBGCrossfade that received the signal
+	 * @window: the #GdkWindow the crossfade happend on.
+	 *
+	 * When a crossfade finishes, @window will have a copy
+	 * of the end surface as its background, and this signal will
+	 * get emitted.
+	 */
+	signals[FINISHED] = g_signal_new ("finished",
+					  G_OBJECT_CLASS_TYPE (gobject_class),
+					  G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					  g_cclosure_marshal_VOID__OBJECT,
+					  G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+	g_type_class_add_private (gobject_class, sizeof (XfceBGCrossfadePrivate));
+}
+
+static void
+xfce_bg_crossfade_init (XfceBGCrossfade *fade)
+{
+	fade->priv = XFCE_BG_CROSSFADE_GET_PRIVATE (fade);
+
+	fade->priv->window = NULL;
+	fade->priv->widget = NULL;
+	fade->priv->fading_surface = NULL;
+	fade->priv->start_surface = NULL;
+	fade->priv->end_surface = NULL;
+	fade->priv->timeout_id = 0;
+}
+
+/**
+ * xfce_bg_crossfade_new:
+ * @width: The width of the crossfading window
+ * @height: The height of the crossfading window
+ *
+ * Creates a new object to manage crossfading a
+ * window background between two #cairo_surface_ts.
+ *
+ * Return value: the new #XfceBGCrossfade
+ **/
+XfceBGCrossfade* xfce_bg_crossfade_new (int width, int height)
+{
+	GObject* object;
+
+	object = g_object_new(XFCE_TYPE_BG_CROSSFADE,
+		"width", width,
+		"height", height,
+		NULL);
+
+	return (XfceBGCrossfade*) object;
+}
+
+static cairo_surface_t *
+tile_surface (cairo_surface_t *surface,
+              int              width,
+              int              height)
+{
+	cairo_surface_t *copy;
+	cairo_t *cr;
+
+	if (surface == NULL)
+	{
+		copy = gdk_window_create_similar_surface (gdk_get_default_root_window (),
+		                                          CAIRO_CONTENT_COLOR,
+		                                          width, height);
+	}
+	else
+	{
+		copy = cairo_surface_create_similar (surface,
+		                                     cairo_surface_get_content (surface),
+		                                     width, height);
+	}
+
+	cr = cairo_create (copy);
+
+	if (surface != NULL)
+	{
+		cairo_pattern_t *pattern;
+		cairo_set_source_surface (cr, surface, 0.0, 0.0);
+		pattern = cairo_get_source (cr);
+		cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
+	}
+	else
+	{
+		GtkStyleContext *context;
+		GdkRGBA bg;
+		context = gtk_style_context_new ();
+		gtk_style_context_add_provider (context,
+										GTK_STYLE_PROVIDER (gtk_css_provider_get_default ()),
+										GTK_STYLE_PROVIDER_PRIORITY_THEME);
+		gtk_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &bg);
+		gdk_cairo_set_source_rgba(cr, &bg);
+		g_object_unref (G_OBJECT (context));
+	}
+
+	cairo_paint (cr);
+
+	if (cairo_status (cr) != CAIRO_STATUS_SUCCESS)
+	{
+		cairo_surface_destroy (copy);
+		copy = NULL;
+	}
+
+	cairo_destroy(cr);
+
+	return copy;
+}
+
+/**
+ * xfce_bg_crossfade_set_start_surface:
+ * @fade: a #XfceBGCrossfade
+ * @surface: The cairo surface to fade from
+ *
+ * Before initiating a crossfade with xfce_bg_crossfade_start()
+ * a start and end surface have to be set.  This function sets
+ * the surface shown at the beginning of the crossfade effect.
+ *
+ * Return value: %TRUE if successful, or %FALSE if the surface
+ * could not be copied.
+ **/
+gboolean
+xfce_bg_crossfade_set_start_surface (XfceBGCrossfade* fade, cairo_surface_t *surface)
+{
+	g_return_val_if_fail (XFCE_IS_BG_CROSSFADE (fade), FALSE);
+
+	if (fade->priv->start_surface != NULL)
+	{
+		cairo_surface_destroy (fade->priv->start_surface);
+		fade->priv->start_surface = NULL;
+	}
+
+	fade->priv->start_surface = tile_surface (surface,
+						  fade->priv->width,
+						  fade->priv->height);
+
+	return fade->priv->start_surface != NULL;
+}
+
+static gdouble
+get_current_time (void)
+{
+	const double microseconds_per_second = (double) G_USEC_PER_SEC;
+	double timestamp;
+	GTimeVal now;
+
+	g_get_current_time (&now);
+
+	timestamp = ((microseconds_per_second * now.tv_sec) + now.tv_usec) /
+	            microseconds_per_second;
+
+	return timestamp;
+}
+
+/**
+ * xfce_bg_crossfade_set_end_surface:
+ * @fade: a #XfceBGCrossfade
+ * @surface: The cairo surface to fade to
+ *
+ * Before initiating a crossfade with xfce_bg_crossfade_start()
+ * a start and end surface have to be set.  This function sets
+ * the surface shown at the end of the crossfade effect.
+ *
+ * Return value: %TRUE if successful, or %FALSE if the surface
+ * could not be copied.
+ **/
+gboolean
+xfce_bg_crossfade_set_end_surface (XfceBGCrossfade* fade, cairo_surface_t *surface)
+{
+	g_return_val_if_fail (XFCE_IS_BG_CROSSFADE (fade), FALSE);
+
+	if (fade->priv->end_surface != NULL) {
+		cairo_surface_destroy (fade->priv->end_surface);
+		fade->priv->end_surface = NULL;
+	}
+
+	fade->priv->end_surface = tile_surface (surface,
+					        fade->priv->width,
+					        fade->priv->height);
+
+	/* Reset timer in case we're called while animating
+	 */
+	fade->priv->start_time = get_current_time ();
+	return fade->priv->end_surface != NULL;
+}
+
+static gboolean
+animations_are_disabled (XfceBGCrossfade *fade)
+{
+	GtkSettings *settings;
+	GdkScreen *screen;
+	gboolean are_enabled;
+
+	g_assert (fade->priv->window != NULL);
+
+	screen = gdk_window_get_screen(fade->priv->window);
+
+	settings = gtk_settings_get_for_screen (screen);
+
+	g_object_get (settings, "gtk-enable-animations", &are_enabled, NULL);
+
+	return !are_enabled;
+}
+
+static void
+send_root_property_change_notification (XfceBGCrossfade *fade)
+{
+        long zero_length_pixmap = 0;
+
+        /* We do a zero length append to force a change notification,
+         * without changing the value */
+        XChangeProperty (GDK_WINDOW_XDISPLAY (fade->priv->window),
+                         GDK_WINDOW_XID (fade->priv->window),
+                         gdk_x11_get_xatom_by_name ("_XROOTPMAP_ID"),
+                         XA_PIXMAP, 32, PropModeAppend,
+                         (unsigned char *) &zero_length_pixmap, 0);
+}
+
+static void
+draw_background (XfceBGCrossfade *fade)
+{
+	if (fade->priv->widget != NULL) {
+		gtk_widget_queue_draw (fade->priv->widget);
+	} else if (gdk_window_get_window_type (fade->priv->window) != GDK_WINDOW_ROOT) {
+		cairo_t           *cr;
+		cairo_region_t    *region;
+		GdkDrawingContext *draw_context;
+
+		region = gdk_window_get_visible_region (fade->priv->window);
+		draw_context = gdk_window_begin_draw_frame (fade->priv->window,
+		                                            region);
+		cr = gdk_drawing_context_get_cairo_context (draw_context);
+		cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+		cairo_set_source_surface (cr, fade->priv->fading_surface, 0, 0);
+		cairo_paint (cr);
+		gdk_window_end_draw_frame (fade->priv->window, draw_context);
+		cairo_region_destroy (region);
+	} else {
+		Display *xdisplay = GDK_WINDOW_XDISPLAY (fade->priv->window);
+		GdkDisplay *display;
+		display = gdk_display_get_default ();
+		gdk_x11_display_error_trap_push (display);
+		XGrabServer (xdisplay);
+		XClearWindow (xdisplay, GDK_WINDOW_XID (fade->priv->window));
+		send_root_property_change_notification (fade);
+		XFlush (xdisplay);
+		XUngrabServer (xdisplay);
+		gdk_x11_display_error_trap_pop_ignored (display);
+	}
+}
+
+static gboolean
+on_widget_draw (GtkWidget       *widget,
+                cairo_t         *cr,
+                XfceBGCrossfade *fade)
+{
+	g_assert (fade->priv->fading_surface != NULL);
+
+	cairo_set_source_surface (cr, fade->priv->fading_surface, 0, 0);
+	cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
+	cairo_paint (cr);
+
+	return FALSE;
+}
+
+static gboolean
+on_tick (XfceBGCrossfade *fade)
+{
+	gdouble now, percent_done;
+	cairo_t *cr;
+	cairo_status_t status;
+
+	g_return_val_if_fail (XFCE_IS_BG_CROSSFADE (fade), FALSE);
+
+	now = get_current_time ();
+
+	percent_done = (now - fade->priv->start_time) / fade->priv->total_duration;
+	percent_done = CLAMP (percent_done, 0.0, 1.0);
+
+	/* If it's taking a long time to get to the first frame,
+	 * then lengthen the duration, so the user will get to see
+	 * the effect.
+	 */
+	if (fade->priv->is_first_frame && percent_done > .33) {
+		fade->priv->is_first_frame = FALSE;
+		fade->priv->total_duration *= 1.5;
+		return on_tick (fade);
+	}
+
+	if (fade->priv->fading_surface == NULL ||
+	    fade->priv->end_surface == NULL) {
+		return FALSE;
+	}
+
+	if (animations_are_disabled (fade)) {
+		return FALSE;
+	}
+
+	/* We accumulate the results in place for performance reasons.
+	 *
+	 * This means 1) The fade is exponential, not linear (looks good!)
+	 * 2) The rate of fade is not independent of frame rate. Slower machines
+	 * will get a slower fade (but never longer than .75 seconds), and
+	 * even the fastest machines will get *some* fade because the framerate
+	 * is capped.
+	 */
+	cr = cairo_create (fade->priv->fading_surface);
+
+	cairo_set_source_surface (cr, fade->priv->end_surface,
+				  0.0, 0.0);
+	cairo_paint_with_alpha (cr, percent_done);
+
+	status = cairo_status (cr);
+	cairo_destroy (cr);
+
+	if (status == CAIRO_STATUS_SUCCESS) {
+		draw_background (fade);
+	}
+	return percent_done <= .99;
+}
+
+static void
+on_finished (XfceBGCrossfade *fade)
+{
+	cairo_t *cr;
+
+	if (fade->priv->timeout_id == 0)
+		return;
+
+	g_assert (fade->priv->fading_surface != NULL);
+	g_assert (fade->priv->end_surface != NULL);
+
+	cr = cairo_create (fade->priv->fading_surface);
+	cairo_set_source_surface (cr, fade->priv->end_surface, 0, 0);
+	cairo_paint (cr);
+	cairo_destroy (cr);
+	draw_background (fade);
+
+	cairo_surface_destroy (fade->priv->fading_surface);
+	fade->priv->fading_surface = NULL;
+
+	cairo_surface_destroy (fade->priv->end_surface);
+	fade->priv->end_surface = NULL;
+
+	g_assert (fade->priv->start_surface != NULL);
+
+	cairo_surface_destroy (fade->priv->start_surface);
+	fade->priv->start_surface = NULL;
+
+	if (fade->priv->widget != NULL) {
+		g_signal_handlers_disconnect_by_func (fade->priv->widget,
+		                                      (GCallback) on_widget_draw,
+		                                      fade);
+	}
+	fade->priv->widget = NULL;
+
+	fade->priv->timeout_id = 0;
+	g_signal_emit (fade, signals[FINISHED], 0, fade->priv->window);
+}
+
+/* This function queries the _XROOTPMAP_ID property from the root window
+ * to determine the current root window background pixmap and returns a
+ * surface to draw directly to it.
+ * If _XROOTPMAP_ID is not set, then NULL returned.
+ */
+static cairo_surface_t *
+get_root_pixmap_id_surface (GdkDisplay *display)
+{
+	GdkScreen       *screen;
+	Display         *xdisplay;
+	Visual          *xvisual;
+	Window           xroot;
+	Atom             type;
+	int              format, result;
+	unsigned long    nitems, bytes_after;
+	unsigned char   *data;
+	cairo_surface_t *surface = NULL;
+
+	g_return_val_if_fail (display != NULL, NULL);
+
+	screen   = gdk_display_get_default_screen (display);
+	xdisplay = GDK_DISPLAY_XDISPLAY (display);
+	xvisual  = GDK_VISUAL_XVISUAL (gdk_screen_get_system_visual (screen));
+	xroot    = RootWindow (xdisplay, GDK_SCREEN_XNUMBER (screen));
+
+	result = XGetWindowProperty (xdisplay, xroot,
+				     gdk_x11_get_xatom_by_name ("_XROOTPMAP_ID"),
+				     0L, 1L, False, XA_PIXMAP,
+				     &type, &format, &nitems, &bytes_after,
+				     &data);
+
+	if (result != Success || type != XA_PIXMAP ||
+	    format != 32 || nitems != 1) {
+		XFree (data);
+		data = NULL;
+	}
+
+	if (data != NULL) {
+		Pixmap pixmap = *(Pixmap *) data;
+		Window root_ret;
+		int x_ret, y_ret;
+		unsigned int w_ret, h_ret, bw_ret, depth_ret;
+
+		gdk_x11_display_error_trap_push (display);
+		if (XGetGeometry (xdisplay, pixmap, &root_ret,
+		                  &x_ret, &y_ret, &w_ret, &h_ret,
+		                  &bw_ret, &depth_ret))
+		{
+			surface = cairo_xlib_surface_create (xdisplay,
+			                                     pixmap, xvisual,
+			                                     w_ret, h_ret);
+		}
+
+		gdk_x11_display_error_trap_pop_ignored (display);
+		XFree (data);
+	}
+
+	gdk_display_flush (display);
+	return surface;
+}
+
+/**
+ * xfce_bg_crossfade_start:
+ * @fade: a #XfceBGCrossfade
+ * @window: The #GdkWindow to draw crossfade on
+ *
+ * This function initiates a quick crossfade between two surfaces on
+ * the background of @window. Before initiating the crossfade both
+ * xfce_bg_crossfade_set_start_surface() and
+ * xfce_bg_crossfade_set_end_surface() need to be called. If animations
+ * are disabled, the crossfade is skipped, and the window background is
+ * set immediately to the end surface.
+ **/
+void
+xfce_bg_crossfade_start (XfceBGCrossfade *fade,
+                         GdkWindow       *window)
+{
+	GSource *source;
+	GMainContext *context;
+
+	g_return_if_fail (XFCE_IS_BG_CROSSFADE (fade));
+	g_return_if_fail (window != NULL);
+	g_return_if_fail (fade->priv->start_surface != NULL);
+	g_return_if_fail (fade->priv->end_surface != NULL);
+	g_return_if_fail (!xfce_bg_crossfade_is_started (fade));
+	g_return_if_fail (gdk_window_get_window_type (window) != GDK_WINDOW_FOREIGN);
+
+	/* If drawing is done on the root window,
+	 * it is essential to have the root pixmap.
+	 */
+	if (gdk_window_get_window_type (window) == GDK_WINDOW_ROOT) {
+		GdkDisplay *display = gdk_window_get_display (window);
+		cairo_surface_t *surface = get_root_pixmap_id_surface (display);
+
+		g_return_if_fail (surface != NULL);
+		cairo_surface_destroy (surface);
+	}
+
+	if (fade->priv->fading_surface != NULL) {
+		cairo_surface_destroy (fade->priv->fading_surface);
+		fade->priv->fading_surface = NULL;
+	}
+
+	fade->priv->window = window;
+	if (gdk_window_get_window_type (fade->priv->window) != GDK_WINDOW_ROOT) {
+		fade->priv->fading_surface = tile_surface (fade->priv->start_surface,
+		                                           fade->priv->width,
+		                                           fade->priv->height);
+		if (fade->priv->widget != NULL) {
+			g_signal_connect (fade->priv->widget, "draw",
+			                  (GCallback) on_widget_draw, fade);
+		}
+	} else {
+		cairo_t   *cr;
+		GdkDisplay *display = gdk_window_get_display (fade->priv->window);
+
+		fade->priv->fading_surface = get_root_pixmap_id_surface (display);
+		cr = cairo_create (fade->priv->fading_surface);
+		cairo_set_source_surface (cr, fade->priv->start_surface, 0, 0);
+		cairo_paint (cr);
+		cairo_destroy (cr);
+	}
+	draw_background (fade);
+
+	source = g_timeout_source_new (1000 / 60.0);
+	g_source_set_callback (source,
+			       (GSourceFunc) on_tick,
+			       fade,
+			       (GDestroyNotify) on_finished);
+	context = g_main_context_default ();
+	fade->priv->timeout_id = g_source_attach (source, context);
+	g_source_unref (source);
+
+	fade->priv->is_first_frame = TRUE;
+	fade->priv->total_duration = .75;
+	fade->priv->start_time = get_current_time ();
+}
+
+/**
+ * xfce_bg_crossfade_start_widget:
+ * @fade: a #XfceBGCrossfade
+ * @widget: The #GtkWidget to draw crossfade on
+ *
+ * This function initiates a quick crossfade between two surfaces on
+ * the background of @widget. Before initiating the crossfade both
+ * xfce_bg_crossfade_set_start_surface() and
+ * xfce_bg_crossfade_set_end_surface() need to be called. If animations
+ * are disabled, the crossfade is skipped, and the window background is
+ * set immediately to the end surface.
+ **/
+void
+xfce_bg_crossfade_start_widget (XfceBGCrossfade *fade,
+                                GtkWidget       *widget)
+{
+	GdkWindow *window;
+
+	g_return_if_fail (XFCE_IS_BG_CROSSFADE (fade));
+	g_return_if_fail (widget != NULL);
+
+	fade->priv->widget = widget;
+	gtk_widget_realize (fade->priv->widget);
+	window = gtk_widget_get_window (fade->priv->widget);
+
+	xfce_bg_crossfade_start (fade, window);
+}
+
+/**
+ * xfce_bg_crossfade_is_started:
+ * @fade: a #XfceBGCrossfade
+ *
+ * This function reveals whether or not @fade is currently
+ * running on a window.  See xfce_bg_crossfade_start() for
+ * information on how to initiate a crossfade.
+ *
+ * Return value: %TRUE if fading, or %FALSE if not fading
+ **/
+gboolean
+xfce_bg_crossfade_is_started (XfceBGCrossfade *fade)
+{
+	g_return_val_if_fail (XFCE_IS_BG_CROSSFADE (fade), FALSE);
+
+	return fade->priv->timeout_id != 0;
+}
+
+/**
+ * xfce_bg_crossfade_stop:
+ * @fade: a #XfceBGCrossfade
+ *
+ * This function stops any in progress crossfades that may be
+ * happening.  It's harmless to call this function if @fade is
+ * already stopped.
+ **/
+void
+xfce_bg_crossfade_stop (XfceBGCrossfade *fade)
+{
+	g_return_if_fail (XFCE_IS_BG_CROSSFADE (fade));
+
+	if (!xfce_bg_crossfade_is_started (fade))
+		return;
+
+	g_assert (fade->priv->timeout_id != 0);
+	g_source_remove (fade->priv->timeout_id);
+	fade->priv->timeout_id = 0;
+}
diff --git a/src/xfce-bg-crossfade.h b/src/xfce-bg-crossfade.h
new file mode 100644
index 0000000..7b19693
--- /dev/null
+++ b/src/xfce-bg-crossfade.h
@@ -0,0 +1,76 @@
+/* xfce-bg-crossfade.h - fade window background between two pixmaps
+
+   Copyright 2008, Red Hat, Inc.
+
+   This file is part of the Mate Library.
+
+   The Mate Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Mate 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Mate Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
+   Floor, Boston, MA 02110-1301 US.
+
+   Author: Ray Strode <rstrode at redhat.com>
+*/
+
+#ifndef __XFCE_BG_CROSSFADE_H__
+#define __XFCE_BG_CROSSFADE_H__
+
+#include <glib.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define XFCE_TYPE_BG_CROSSFADE            (xfce_bg_crossfade_get_type ())
+#define XFCE_BG_CROSSFADE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), XFCE_TYPE_BG_CROSSFADE, XfceBGCrossfade))
+#define XFCE_BG_CROSSFADE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  XFCE_TYPE_BG_CROSSFADE, XfceBGCrossfadeClass))
+#define XFCE_IS_BG_CROSSFADE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), XFCE_TYPE_BG_CROSSFADE))
+#define XFCE_IS_BG_CROSSFADE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  XFCE_TYPE_BG_CROSSFADE))
+#define XFCE_BG_CROSSFADE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  XFCE_TYPE_BG_CROSSFADE, XfceBGCrossfadeClass))
+
+typedef struct _XfceBGCrossfadePrivate XfceBGCrossfadePrivate;
+typedef struct _XfceBGCrossfade XfceBGCrossfade;
+typedef struct _XfceBGCrossfadeClass XfceBGCrossfadeClass;
+
+struct _XfceBGCrossfade
+{
+	GObject parent_object;
+
+	XfceBGCrossfadePrivate *priv;
+};
+
+struct _XfceBGCrossfadeClass
+{
+	GObjectClass parent_class;
+
+	void (* finished) (XfceBGCrossfade *fade, GdkWindow *window);
+};
+
+GType             xfce_bg_crossfade_get_type              (void);
+XfceBGCrossfade *xfce_bg_crossfade_new (int width, int height);
+
+gboolean          xfce_bg_crossfade_set_start_surface (XfceBGCrossfade *fade,
+						       cairo_surface_t *surface);
+gboolean          xfce_bg_crossfade_set_end_surface (XfceBGCrossfade *fade,
+						     cairo_surface_t *surface);
+
+void              xfce_bg_crossfade_start (XfceBGCrossfade *fade,
+                                           GdkWindow        *window);
+void              xfce_bg_crossfade_start_widget (XfceBGCrossfade *fade,
+                                                  GtkWidget       *widget);
+gboolean          xfce_bg_crossfade_is_started (XfceBGCrossfade *fade);
+void              xfce_bg_crossfade_stop (XfceBGCrossfade *fade);
+
+G_END_DECLS
+
+#endif
diff --git a/src/xfce-bg.c b/src/xfce-bg.c
new file mode 100644
index 0000000..6e04b0c
--- /dev/null
+++ b/src/xfce-bg.c
@@ -0,0 +1,3262 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+matebg.c: Object for the desktop background.
+
+Copyright (C) 2000 Eazel, Inc.
+Copyright (C) 2007-2008 Red Hat, Inc.
+Copyright (C) 2012 Jasmine Hassan <jasmine.aura at gmail.com>
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+This program 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this program; if not, write to the
+Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+Boston, MA 02110-1301, USA.
+
+Derived from eel-background.c and eel-gdk-pixbuf-extensions.c by
+Darin Adler <darin at eazel.com> and Ramiro Estrugo <ramiro at eazel.com>
+
+Authors: Soren Sandmann <sandmann at redhat.com>
+	 Jasmine Hassan <jasmine.aura at gmail.com>
+
+*/
+
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include <cairo.h>
+
+#include <xfce-bg.h>
+#include <xfce-bg-crossfade.h>
+
+# include <cairo-xlib.h>
+
+#define XFCE_BG_CACHE_DIR "mate/background"
+
+/* We keep the large pixbufs around if the next update
+   in the slideshow is less than 60 seconds away */
+#define KEEP_EXPENSIVE_CACHE_SECS 60
+
+typedef struct _SlideShow SlideShow;
+typedef struct _Slide Slide;
+
+struct _Slide {
+	double duration; /* in seconds */
+	gboolean fixed;
+
+	GSList* file1;
+	GSList* file2; /* NULL if fixed is TRUE */
+};
+
+typedef struct _FileSize FileSize;
+struct _FileSize {
+	gint width;
+	gint height;
+
+	char* file;
+};
+
+/* This is the size of the GdkRGB dither matrix, in order to avoid
+ * bad dithering when tiling the gradient
+ */
+#define GRADIENT_PIXMAP_TILE_SIZE 128
+
+typedef struct FileCacheEntry FileCacheEntry;
+#define CACHE_SIZE 4
+
+/*
+ *   Implementation of the XfceBG class
+ */
+struct _XfceBG {
+	GObject		 parent_instance;
+	char		*filename;
+	XfceBGPlacement	 placement;
+	XfceBGColorType	 color_type;
+	GdkRGBA	 	 primary;
+	GdkRGBA	 	 secondary;
+	gboolean	 is_enabled;
+
+	GFileMonitor* file_monitor;
+
+	guint changed_id;
+	guint transitioned_id;
+	guint blow_caches_id;
+
+	/* Cached information, only access through cache accessor functions */
+	SlideShow* slideshow;
+	time_t file_mtime;
+	GdkPixbuf* pixbuf_cache;
+	int timeout_id;
+
+	GList* file_cache;
+};
+
+struct _XfceBGClass {
+	GObjectClass parent_class;
+};
+
+enum {
+	CHANGED,
+	TRANSITIONED,
+	N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = {0};
+
+G_DEFINE_TYPE(XfceBG, xfce_bg, G_TYPE_OBJECT)
+
+static cairo_surface_t *make_root_pixmap     (GdkWindow  *window,
+                                              gint        width,
+                                              gint        height);
+
+/* Pixbuf utils */
+static void       pixbuf_average_value (GdkPixbuf  *pixbuf,
+                                        GdkRGBA    *result);
+static GdkPixbuf *pixbuf_scale_to_fit  (GdkPixbuf  *src,
+					int         max_width,
+					int         max_height);
+static GdkPixbuf *pixbuf_scale_to_min  (GdkPixbuf  *src,
+					int         min_width,
+					int         min_height);
+
+static void       pixbuf_draw_gradient (GdkPixbuf    *pixbuf,
+					gboolean      horizontal,
+					GdkRGBA     *c1,
+					GdkRGBA     *c2,
+					GdkRectangle *rect);
+
+static void       pixbuf_tile          (GdkPixbuf  *src,
+					GdkPixbuf  *dest);
+static void       pixbuf_blend         (GdkPixbuf  *src,
+					GdkPixbuf  *dest,
+					int         src_x,
+					int         src_y,
+					int         width,
+					int         height,
+					int         dest_x,
+					int         dest_y,
+					double      alpha);
+
+/* Thumbnail utilities */
+static GdkPixbuf *create_thumbnail_for_filename (XfceDesktopThumbnailFactory *factory,
+						 const char            *filename);
+static gboolean   get_thumb_annotations (GdkPixbuf             *thumb,
+					 int                   *orig_width,
+					 int                   *orig_height);
+
+/* Cache */
+static GdkPixbuf *get_pixbuf_for_size  (XfceBG               *bg,
+					gint                  num_monitor,
+					int                   width,
+					int                   height);
+static void       clear_cache          (XfceBG               *bg);
+static gboolean   is_different         (XfceBG               *bg,
+					const char            *filename);
+static time_t     get_mtime            (const char            *filename);
+static GdkPixbuf *create_img_thumbnail (XfceBG               *bg,
+					XfceDesktopThumbnailFactory *factory,
+					GdkScreen             *screen,
+					int                    dest_width,
+					int                    dest_height,
+					int		       frame_num);
+static SlideShow * get_as_slideshow    (XfceBG               *bg,
+					const char 	      *filename);
+static Slide *     get_current_slide   (SlideShow 	      *show,
+		   			double    	      *alpha);
+static gboolean    slideshow_has_multiple_sizes (SlideShow *show);
+
+static SlideShow *read_slideshow_file (const char *filename,
+				       GError     **err);
+static SlideShow *slideshow_ref       (SlideShow  *show);
+static void       slideshow_unref     (SlideShow  *show);
+
+static FileSize   *find_best_size      (GSList                *sizes,
+					gint                   width,
+					gint                   height);
+
+static void
+color_from_string (const char *string,
+		   GdkRGBA   *colorp)
+{
+	/* If all else fails use black */
+	gdk_rgba_parse (colorp, "#000000");
+
+	if (!string)
+		return;
+
+	gdk_rgba_parse (colorp, string);
+}
+
+static char *
+color_to_string (const GdkRGBA *color)
+{
+	return g_strdup_printf ("#%02x%02x%02x",
+				((guint) (color->red * 65535)) >> 8,
+				((guint) (color->green * 65535)) >> 8,
+				((guint) (color->blue * 65535)) >> 8);
+}
+
+static gboolean
+do_changed (XfceBG *bg)
+{
+	bg->changed_id = 0;
+
+	g_signal_emit (G_OBJECT (bg), signals[CHANGED], 0);
+
+	return FALSE;
+}
+
+static void
+queue_changed (XfceBG *bg)
+{
+	if (bg->changed_id > 0) {
+		g_source_remove (bg->changed_id);
+	}
+
+	bg->changed_id = g_timeout_add_full (G_PRIORITY_LOW,
+					     100,
+					     (GSourceFunc)do_changed,
+					     bg,
+					     NULL);
+}
+
+static gboolean
+do_transitioned (XfceBG *bg)
+{
+	bg->transitioned_id = 0;
+
+	if (bg->pixbuf_cache) {
+		g_object_unref (bg->pixbuf_cache);
+		bg->pixbuf_cache = NULL;
+	}
+
+	g_signal_emit (G_OBJECT (bg), signals[TRANSITIONED], 0);
+
+	return FALSE;
+}
+
+static void
+queue_transitioned (XfceBG *bg)
+{
+	if (bg->transitioned_id > 0) {
+		g_source_remove (bg->transitioned_id);
+	}
+
+	bg->transitioned_id = g_timeout_add_full (G_PRIORITY_LOW,
+						  100,
+						  (GSourceFunc)do_transitioned,
+						  bg,
+						  NULL);
+}
+
+/* This function loads the user's preferences */
+void
+xfce_bg_load_from_preferences (XfceBG *bg)
+{
+	GSettings *settings;
+	settings = g_settings_new (XFCE_BG_SCHEMA);
+
+	xfce_bg_load_from_gsettings (bg, settings);
+	g_object_unref (settings);
+
+	/* Queue change to force background redraw */
+	queue_changed (bg);
+}
+
+/* This function loads default system settings */
+void
+xfce_bg_load_from_system_preferences (XfceBG *bg)
+{
+	GSettings *settings;
+
+	/* FIXME: we need to bind system settings instead of user but
+	* that's currently impossible, not implemented yet.
+	* Hence, reset to system default values.
+	*/
+	settings = g_settings_new (XFCE_BG_SCHEMA);
+
+	xfce_bg_load_from_system_gsettings (bg, settings, FALSE);
+
+	g_object_unref (settings);
+}
+
+/* This function loads (and optionally resets to) default system settings */
+void
+xfce_bg_load_from_system_gsettings (XfceBG    *bg,
+				    GSettings *settings,
+				    gboolean   reset_apply)
+{
+	gchar **keys;
+	gchar **k;
+
+	g_return_if_fail (XFCE_IS_BG (bg));
+	g_return_if_fail (G_IS_SETTINGS (settings));
+
+	g_settings_delay (settings);
+
+	keys = g_settings_list_keys (settings);
+		for (k = keys; *k; k++) {
+			g_settings_reset (settings, *k);
+	}
+	g_strfreev (keys);
+
+	if (reset_apply) {
+		/* Apply changes atomically. */
+		g_settings_apply (settings);
+	} else {
+		xfce_bg_load_from_gsettings (bg, settings);
+		g_settings_revert (settings);
+	}
+}
+
+void
+xfce_bg_load_from_gsettings (XfceBG    *bg,
+			     GSettings *settings)
+{
+	char    *tmp;
+	char    *filename;
+	XfceBGColorType ctype;
+	GdkRGBA c1, c2;
+	XfceBGPlacement placement;
+
+	g_return_if_fail (XFCE_IS_BG (bg));
+	g_return_if_fail (G_IS_SETTINGS (settings));
+
+	bg->is_enabled = g_settings_get_boolean (settings, XFCE_BG_KEY_DRAW_BACKGROUND);
+
+	/* Filename */
+	filename = NULL;
+	tmp = g_settings_get_string (settings, XFCE_BG_KEY_PICTURE_FILENAME);
+	if (tmp && *tmp != '\0') {
+		/* FIXME: UTF-8 checks should go away.
+		 * picture-filename is of type string, which can only be used for
+		 * UTF-8 strings, and some filenames are not, dependending on the
+		 * locale used.
+		 * It would be better (and simpler) to change to a URI instead,
+		 * as URIs are UTF-8 encoded strings.
+		 */
+		if (g_utf8_validate (tmp, -1, NULL) &&
+		    g_file_test (tmp, G_FILE_TEST_EXISTS)) {
+			filename = g_strdup (tmp);
+		} else {
+			filename = g_filename_from_utf8 (tmp, -1, NULL, NULL, NULL);
+		}
+
+		/* Fallback to default BG if the filename set is non-existent */
+		if (filename != NULL && !g_file_test (filename, G_FILE_TEST_EXISTS)) {
+
+			g_free (filename);
+
+			g_settings_delay (settings);
+			g_settings_reset (settings, XFCE_BG_KEY_PICTURE_FILENAME);
+			filename = g_settings_get_string (settings, XFCE_BG_KEY_PICTURE_FILENAME);
+			g_settings_revert (settings);
+
+			//* Check if default background exists, also */
+			if (filename != NULL && !g_file_test (filename, G_FILE_TEST_EXISTS)) {
+				g_free (filename);
+				filename = NULL;
+			}
+		}
+	}
+	g_free (tmp);
+
+	/* Colors */
+	tmp = g_settings_get_string (settings, XFCE_BG_KEY_PRIMARY_COLOR);
+	color_from_string (tmp, &c1);
+	g_free (tmp);
+
+	tmp = g_settings_get_string (settings, XFCE_BG_KEY_SECONDARY_COLOR);
+	color_from_string (tmp, &c2);
+	g_free (tmp);
+
+	/* Color type */
+	ctype = g_settings_get_enum (settings, XFCE_BG_KEY_COLOR_TYPE);
+
+	/* Placement */
+	placement = g_settings_get_enum (settings, XFCE_BG_KEY_PICTURE_PLACEMENT);
+
+	xfce_bg_set_color (bg, ctype, &c1, &c2);
+	xfce_bg_set_placement (bg, placement);
+	xfce_bg_set_filename (bg, filename);
+
+	if (filename != NULL)
+		g_free (filename);
+}
+
+void
+xfce_bg_save_to_preferences (XfceBG *bg)
+{
+	GSettings *settings;
+	settings = g_settings_new (XFCE_BG_SCHEMA);
+
+	xfce_bg_save_to_gsettings (bg, settings);
+	g_object_unref (settings);
+}
+
+void
+xfce_bg_save_to_gsettings (XfceBG    *bg,
+			   GSettings *settings)
+{
+	gchar *primary;
+	gchar *secondary;
+
+	g_return_if_fail (XFCE_IS_BG (bg));
+	g_return_if_fail (G_IS_SETTINGS (settings));
+
+	primary = color_to_string (&bg->primary);
+	secondary = color_to_string (&bg->secondary);
+
+	g_settings_delay (settings);
+
+	g_settings_set_boolean (settings, XFCE_BG_KEY_DRAW_BACKGROUND, bg->is_enabled);
+	g_settings_set_string (settings, XFCE_BG_KEY_PICTURE_FILENAME, bg->filename);
+	g_settings_set_enum (settings, XFCE_BG_KEY_PICTURE_PLACEMENT, bg->placement);
+	g_settings_set_string (settings, XFCE_BG_KEY_PRIMARY_COLOR, primary);
+	g_settings_set_string (settings, XFCE_BG_KEY_SECONDARY_COLOR, secondary);
+	g_settings_set_enum (settings, XFCE_BG_KEY_COLOR_TYPE, bg->color_type);
+
+	/* Apply changes atomically. */
+	g_settings_apply (settings);
+
+	g_free (primary);
+	g_free (secondary);
+}
+
+
+static void
+xfce_bg_init (XfceBG *bg)
+{
+}
+
+static void
+xfce_bg_dispose (GObject *object)
+{
+	XfceBG *bg = XFCE_BG (object);
+
+	if (bg->file_monitor) {
+		g_object_unref (bg->file_monitor);
+		bg->file_monitor = NULL;
+	}
+
+	clear_cache (bg);
+
+	G_OBJECT_CLASS (xfce_bg_parent_class)->dispose (object);
+}
+
+static void
+xfce_bg_finalize (GObject *object)
+{
+	XfceBG *bg = XFCE_BG (object);
+
+	if (bg->changed_id != 0) {
+		g_source_remove (bg->changed_id);
+		bg->changed_id = 0;
+	}
+
+	if (bg->transitioned_id != 0) {
+		g_source_remove (bg->transitioned_id);
+		bg->transitioned_id = 0;
+	}
+
+	if (bg->blow_caches_id != 0) {
+		g_source_remove (bg->blow_caches_id);
+		bg->blow_caches_id = 0;
+	}
+
+	g_free (bg->filename);
+	bg->filename = NULL;
+
+	G_OBJECT_CLASS (xfce_bg_parent_class)->finalize (object);
+}
+
+static void
+xfce_bg_class_init (XfceBGClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = xfce_bg_dispose;
+	object_class->finalize = xfce_bg_finalize;
+
+	signals[CHANGED] = g_signal_new ("changed",
+					 G_OBJECT_CLASS_TYPE (object_class),
+					 G_SIGNAL_RUN_LAST,
+					 0,
+					 NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+
+	signals[TRANSITIONED] = g_signal_new ("transitioned",
+					 G_OBJECT_CLASS_TYPE (object_class),
+					 G_SIGNAL_RUN_LAST,
+					 0,
+					 NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+}
+
+XfceBG *
+xfce_bg_new (void)
+{
+	return g_object_new (XFCE_TYPE_BG, NULL);
+}
+
+void
+xfce_bg_set_color (XfceBG *bg,
+		    XfceBGColorType type,
+		    GdkRGBA *primary,
+		    GdkRGBA *secondary)
+{
+	g_return_if_fail (bg != NULL);
+	g_return_if_fail (primary != NULL);
+
+	if (bg->color_type != type			||
+	    !gdk_rgba_equal (&bg->primary, primary)			||
+	    (secondary && !gdk_rgba_equal (&bg->secondary, secondary))) {
+		bg->color_type = type;
+		bg->primary = *primary;
+		if (secondary) {
+			bg->secondary = *secondary;
+		}
+
+		queue_changed (bg);
+	}
+}
+
+void
+xfce_bg_set_placement (XfceBG		*bg,
+		       XfceBGPlacement	 placement)
+{
+	g_return_if_fail (bg != NULL);
+
+	if (bg->placement != placement) {
+		bg->placement = placement;
+
+		queue_changed (bg);
+	}
+}
+
+XfceBGPlacement
+xfce_bg_get_placement (XfceBG *bg)
+{
+	g_return_val_if_fail (bg != NULL, -1);
+
+	return bg->placement;
+}
+
+void
+xfce_bg_get_color (XfceBG		*bg,
+		   XfceBGColorType	*type,
+		   GdkRGBA		*primary,
+		   GdkRGBA		*secondary)
+{
+	g_return_if_fail (bg != NULL);
+
+	if (type)
+		*type = bg->color_type;
+
+	if (primary)
+		*primary = bg->primary;
+
+	if (secondary)
+		*secondary = bg->secondary;
+}
+
+void
+xfce_bg_set_draw_background (XfceBG	*bg,
+			     gboolean	 draw_background)
+{
+	g_return_if_fail (bg != NULL);
+
+	if (bg->is_enabled != draw_background) {
+		bg->is_enabled = draw_background;
+
+		queue_changed (bg);
+	}
+}
+
+gboolean
+xfce_bg_get_draw_background (XfceBG *bg)
+{
+	g_return_val_if_fail (bg != NULL, FALSE);
+
+	return bg->is_enabled;
+}
+
+const gchar *
+xfce_bg_get_filename (XfceBG *bg)
+{
+	g_return_val_if_fail (bg != NULL, NULL);
+
+	return bg->filename;
+}
+
+static inline gchar *
+get_wallpaper_cache_dir ()
+{
+	return g_build_filename (g_get_user_cache_dir(), XFCE_BG_CACHE_DIR, NULL);
+}
+
+static inline gchar *
+get_wallpaper_cache_prefix_name (gint                     num_monitor,
+				 XfceBGPlacement          placement,
+				 gint                     width,
+				 gint                     height)
+{
+	return g_strdup_printf ("%i_%i_%i_%i", num_monitor, (gint) placement, width, height);
+}
+
+static char *
+get_wallpaper_cache_filename (const char              *filename,
+			      gint                     num_monitor,
+			      XfceBGPlacement          placement,
+			      gint                     width,
+			      gint                     height)
+{
+	gchar *cache_filename;
+	gchar *cache_prefix_name;
+	gchar *md5_filename;
+	gchar *cache_basename;
+	gchar *cache_dir;
+
+	md5_filename = g_compute_checksum_for_data (G_CHECKSUM_MD5, (const guchar *) filename,
+						    strlen (filename));
+	cache_prefix_name = get_wallpaper_cache_prefix_name (num_monitor, placement, width, height);
+	cache_basename = g_strdup_printf ("%s_%s", cache_prefix_name, md5_filename);
+	cache_dir = get_wallpaper_cache_dir ();
+	cache_filename = g_build_filename (cache_dir, cache_basename, NULL);
+
+	g_free (cache_prefix_name);
+	g_free (md5_filename);
+	g_free (cache_basename);
+	g_free (cache_dir);
+
+	return cache_filename;
+}
+
+static void
+cleanup_cache_for_monitor (gchar *cache_dir,
+			   gint   num_monitor)
+{
+	GDir            *g_cache_dir;
+	gchar           *monitor_prefix;
+	const gchar     *file;
+
+	g_cache_dir = g_dir_open (cache_dir, 0, NULL);
+	monitor_prefix = g_strdup_printf ("%i_", num_monitor);
+
+	file = g_dir_read_name (g_cache_dir);
+	while (file != NULL) {
+		gchar *path = g_build_filename (cache_dir, file, NULL);
+
+		/* purge files with same monitor id */
+		if (g_str_has_prefix (file, monitor_prefix) &&
+		    g_file_test (path, G_FILE_TEST_IS_REGULAR))
+			g_unlink (path);
+
+		g_free (path);
+
+		file = g_dir_read_name (g_cache_dir);
+	}
+
+	g_free (monitor_prefix);
+	g_dir_close (g_cache_dir);
+}
+
+static gboolean
+cache_file_is_valid (const char *filename,
+		     const char *cache_filename)
+{
+	time_t mtime;
+	time_t cache_mtime;
+
+	if (!g_file_test (cache_filename, G_FILE_TEST_IS_REGULAR))
+		return FALSE;
+
+	mtime = get_mtime (filename);
+	cache_mtime = get_mtime (cache_filename);
+
+	return (mtime < cache_mtime);
+}
+
+static void
+refresh_cache_file (XfceBG     *bg,
+		    GdkPixbuf  *new_pixbuf,
+		    gint        num_monitor,
+		    gint        width,
+		    gint        height)
+{
+	gchar           *cache_filename;
+	gchar           *cache_dir;
+	GdkPixbufFormat *format;
+	gchar           *format_name;
+
+	if ((num_monitor == -1) || (width <= 300) || (height <= 300))
+		return;
+
+	cache_filename = get_wallpaper_cache_filename (bg->filename, num_monitor,
+							bg->placement, width, height);
+	cache_dir = get_wallpaper_cache_dir ();
+
+	/* Only refresh scaled file on disk if useful (and don't cache slideshow) */
+	if (!cache_file_is_valid (bg->filename, cache_filename)) {
+		format = gdk_pixbuf_get_file_info (bg->filename, NULL, NULL);
+
+		if (format != NULL) {
+			if (!g_file_test (cache_dir, G_FILE_TEST_IS_DIR)) {
+				g_mkdir_with_parents (cache_dir, 0700);
+			} else {
+				cleanup_cache_for_monitor (cache_dir, num_monitor);
+			}
+
+			format_name = gdk_pixbuf_format_get_name (format);
+
+			if (strcmp (format_name, "jpeg") == 0)
+				gdk_pixbuf_save (new_pixbuf, cache_filename, format_name,
+						 NULL, "quality", "100", NULL);
+			else
+				gdk_pixbuf_save (new_pixbuf, cache_filename, format_name,
+						 NULL, NULL);
+
+			g_free (format_name);
+		}
+	}
+
+	g_free (cache_filename);
+	g_free (cache_dir);
+}
+
+static void
+file_changed (GFileMonitor     *file_monitor,
+	      GFile            *child,
+	      GFile            *other_file,
+	      GFileMonitorEvent event_type,
+	      gpointer          user_data)
+{
+	XfceBG *bg = XFCE_BG (user_data);
+
+	clear_cache (bg);
+	queue_changed (bg);
+}
+
+void
+xfce_bg_set_filename (XfceBG	 *bg,
+		      const char *filename)
+{
+	g_return_if_fail (bg != NULL);
+
+	if (is_different (bg, filename)) {
+		g_free (bg->filename);
+
+		bg->filename = g_strdup (filename);
+		bg->file_mtime = get_mtime (bg->filename);
+
+		if (bg->file_monitor) {
+			g_object_unref (bg->file_monitor);
+			bg->file_monitor = NULL;
+		}
+
+		if (bg->filename) {
+			GFile *f = g_file_new_for_path (bg->filename);
+
+			bg->file_monitor = g_file_monitor_file (f, 0, NULL, NULL);
+			g_signal_connect (bg->file_monitor, "changed",
+					  G_CALLBACK (file_changed), bg);
+
+			g_object_unref (f);
+		}
+
+		clear_cache (bg);
+
+		queue_changed (bg);
+	}
+}
+
+static void
+draw_color_area (XfceBG       *bg,
+		 GdkPixbuf    *dest,
+		 GdkRectangle *rect)
+{
+	guint32 pixel;
+	GdkRectangle extent;
+
+        extent.x = 0;
+        extent.y = 0;
+        extent.width = gdk_pixbuf_get_width (dest);
+        extent.height = gdk_pixbuf_get_height (dest);
+
+	gdk_rectangle_intersect (rect, &extent, rect);
+
+	switch (bg->color_type) {
+	case XFCE_BG_COLOR_SOLID:
+		/* not really a big deal to ignore the area of interest */
+		pixel = ((guint) (bg->primary.red * 0xff) << 24)   |
+			((guint) (bg->primary.green * 0xff) << 16) |
+			((guint) (bg->primary.blue * 0xff) << 8)   |
+			(0xff);
+
+		gdk_pixbuf_fill (dest, pixel);
+		break;
+
+	case XFCE_BG_COLOR_H_GRADIENT:
+		pixbuf_draw_gradient (dest, TRUE, &(bg->primary), &(bg->secondary), rect);
+		break;
+
+	case XFCE_BG_COLOR_V_GRADIENT:
+		pixbuf_draw_gradient (dest, FALSE, &(bg->primary), &(bg->secondary), rect);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static void
+draw_color (XfceBG    *bg,
+	    GdkPixbuf *dest)
+{
+	GdkRectangle rect;
+
+	rect.x = 0;
+	rect.y = 0;
+	rect.width = gdk_pixbuf_get_width (dest);
+	rect.height = gdk_pixbuf_get_height (dest);
+	draw_color_area (bg, dest, &rect);
+}
+
+static void
+draw_color_each_monitor (XfceBG    *bg,
+			 GdkPixbuf *dest,
+			 GdkScreen *screen)
+{
+	GdkDisplay *display;
+	GdkRectangle rect;
+	gint num_monitors;
+	int monitor;
+
+	display = gdk_screen_get_display (screen);
+	num_monitors = gdk_display_get_n_monitors (display);
+	for (monitor = 0; monitor < num_monitors; monitor++) {
+		gdk_monitor_get_geometry (gdk_display_get_monitor (display, monitor), &rect);
+		draw_color_area (bg, dest, &rect);
+	}
+}
+
+static GdkPixbuf *
+pixbuf_clip_to_fit (GdkPixbuf *src,
+		    int        max_width,
+		    int        max_height)
+{
+	int src_width, src_height;
+	int w, h;
+	int src_x, src_y;
+	GdkPixbuf *pixbuf;
+
+	src_width = gdk_pixbuf_get_width (src);
+	src_height = gdk_pixbuf_get_height (src);
+
+	if (src_width < max_width && src_height < max_height)
+		return g_object_ref (src);
+
+	w = MIN(src_width, max_width);
+	h = MIN(src_height, max_height);
+
+	pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+				 gdk_pixbuf_get_has_alpha (src),
+				 8, w, h);
+
+	src_x = (src_width - w) / 2;
+	src_y = (src_height - h) / 2;
+	gdk_pixbuf_copy_area (src,
+			      src_x, src_y,
+			      w, h,
+			      pixbuf,
+			      0, 0);
+	return pixbuf;
+}
+
+static GdkPixbuf *
+get_scaled_pixbuf (XfceBGPlacement  placement,
+		   GdkPixbuf       *pixbuf,
+		   int width, int height,
+		   int *x, int *y,
+		   int *w, int *h)
+{
+	GdkPixbuf *new;
+
+#if 0
+	g_print ("original_width: %d %d\n",
+		 gdk_pixbuf_get_width (pixbuf),
+		 gdk_pixbuf_get_height (pixbuf));
+#endif
+
+	switch (placement) {
+	case XFCE_BG_PLACEMENT_SPANNED:
+                new = pixbuf_scale_to_fit (pixbuf, width, height);
+		break;
+	case XFCE_BG_PLACEMENT_ZOOMED:
+		new = pixbuf_scale_to_min (pixbuf, width, height);
+		break;
+
+	case XFCE_BG_PLACEMENT_FILL_SCREEN:
+		new = gdk_pixbuf_scale_simple (pixbuf, width, height,
+					       GDK_INTERP_BILINEAR);
+		break;
+
+	case XFCE_BG_PLACEMENT_SCALED:
+		new = pixbuf_scale_to_fit (pixbuf, width, height);
+		break;
+
+	case XFCE_BG_PLACEMENT_CENTERED:
+	case XFCE_BG_PLACEMENT_TILED:
+	default:
+		new = pixbuf_clip_to_fit (pixbuf, width, height);
+		break;
+	}
+
+	*w = gdk_pixbuf_get_width (new);
+	*h = gdk_pixbuf_get_height (new);
+	*x = (width - *w) / 2;
+	*y = (height - *h) / 2;
+
+	return new;
+}
+
+
+static void
+draw_image_area (XfceBG        *bg,
+		 gint           num_monitor,
+		 GdkPixbuf     *pixbuf,
+		 GdkPixbuf     *dest,
+		 GdkRectangle  *area)
+{
+	int dest_width = area->width;
+	int dest_height = area->height;
+	int x, y, w, h;
+	GdkPixbuf *scaled;
+
+	if (!pixbuf)
+		return;
+
+	scaled = get_scaled_pixbuf (bg->placement, pixbuf, dest_width, dest_height, &x, &y, &w, &h);
+
+	switch (bg->placement) {
+	case XFCE_BG_PLACEMENT_TILED:
+		pixbuf_tile (scaled, dest);
+		break;
+	case XFCE_BG_PLACEMENT_ZOOMED:
+	case XFCE_BG_PLACEMENT_CENTERED:
+	case XFCE_BG_PLACEMENT_FILL_SCREEN:
+	case XFCE_BG_PLACEMENT_SCALED:
+		pixbuf_blend (scaled, dest, 0, 0, w, h, x + area->x, y + area->y, 1.0);
+		break;
+	case XFCE_BG_PLACEMENT_SPANNED:
+		pixbuf_blend (scaled, dest, 0, 0, w, h, x, y, 1.0);
+		break;
+	default:
+		g_assert_not_reached ();
+		break;
+	}
+
+	refresh_cache_file (bg, scaled, num_monitor, dest_width, dest_height);
+
+	g_object_unref (scaled);
+}
+
+static void
+draw_image_for_thumb (XfceBG     *bg,
+		      GdkPixbuf  *pixbuf,
+		      GdkPixbuf  *dest)
+{
+	GdkRectangle rect;
+
+	rect.x = 0;
+	rect.y = 0;
+	rect.width = gdk_pixbuf_get_width (dest);
+	rect.height = gdk_pixbuf_get_height (dest);
+
+	draw_image_area (bg, -1, pixbuf, dest, &rect);
+}
+
+static void
+draw_once (XfceBG    *bg,
+	   GdkPixbuf *dest,
+	   gboolean   is_root)
+{
+	GdkRectangle rect;
+	GdkPixbuf   *pixbuf;
+	gint         monitor;
+
+	/* whether we're drawing on root window or normal (Caja) window */
+	monitor = (is_root) ? 0 : -1;
+
+	rect.x = 0;
+	rect.y = 0;
+	rect.width = gdk_pixbuf_get_width (dest);
+	rect.height = gdk_pixbuf_get_height (dest);
+
+	pixbuf = get_pixbuf_for_size (bg, monitor, rect.width, rect.height);
+	if (pixbuf) {
+		draw_image_area (bg, monitor, pixbuf, dest, &rect);
+
+		g_object_unref (pixbuf);
+	}
+}
+
+static void
+draw_each_monitor (XfceBG    *bg,
+		   GdkPixbuf *dest,
+		   GdkScreen *screen)
+{
+	GdkDisplay *display;
+
+	display = gdk_screen_get_display (screen);
+	gint num_monitors = gdk_display_get_n_monitors (display);
+	gint monitor = 0;
+
+	for (; monitor < num_monitors; monitor++) {
+		GdkRectangle rect;
+		GdkPixbuf *pixbuf;
+
+		gdk_monitor_get_geometry (gdk_display_get_monitor (display, monitor), &rect);
+
+		pixbuf = get_pixbuf_for_size (bg, monitor, rect.width, rect.height);
+		if (pixbuf) {
+			draw_image_area (bg, monitor, pixbuf, dest, &rect);
+
+			g_object_unref (pixbuf);
+		}
+	}
+}
+
+void
+xfce_bg_draw (XfceBG     *bg,
+	       GdkPixbuf *dest,
+	       GdkScreen *screen,
+	       gboolean   is_root)
+{
+	if (!bg)
+		return;
+
+	if (is_root && (bg->placement != XFCE_BG_PLACEMENT_SPANNED)) {
+		draw_color_each_monitor (bg, dest, screen);
+		if (bg->filename) {
+			draw_each_monitor (bg, dest, screen);
+		}
+	} else {
+		draw_color (bg, dest);
+		if (bg->filename) {
+			draw_once (bg, dest, is_root);
+		}
+	}
+}
+
+gboolean
+xfce_bg_has_multiple_sizes (XfceBG *bg)
+{
+	SlideShow *show;
+	gboolean ret;
+
+	g_return_val_if_fail (bg != NULL, FALSE);
+
+	ret = FALSE;
+
+	show = get_as_slideshow (bg, bg->filename);
+	if (show) {
+		ret = slideshow_has_multiple_sizes (show);
+		slideshow_unref (show);
+	}
+
+	return ret;
+}
+
+static void
+xfce_bg_get_pixmap_size (XfceBG   *bg,
+			  int        width,
+			  int        height,
+			  int       *pixmap_width,
+			  int       *pixmap_height)
+{
+	int dummy;
+
+	if (!pixmap_width)
+		pixmap_width = &dummy;
+	if (!pixmap_height)
+		pixmap_height = &dummy;
+
+	*pixmap_width = width;
+	*pixmap_height = height;
+
+	if (!bg->filename) {
+		switch (bg->color_type) {
+		case XFCE_BG_COLOR_SOLID:
+			*pixmap_width = 1;
+			*pixmap_height = 1;
+			break;
+
+		case XFCE_BG_COLOR_H_GRADIENT:
+		case XFCE_BG_COLOR_V_GRADIENT:
+			break;
+		}
+
+		return;
+	}
+}
+
+/**
+ * xfce_bg_create_surface:
+ * @bg: XfceBG
+ * @window:
+ * @width:
+ * @height:
+ * @root:
+ *
+ * Create a surface that can be set as background for @window. If @is_root is
+ * TRUE, the surface created will be created by a temporary X server connection
+ * so that if someone calls XKillClient on it, it won't affect the application
+ * who created it.
+ **/
+cairo_surface_t *
+xfce_bg_create_surface (XfceBG      *bg,
+		 	GdkWindow   *window,
+			int	     width,
+			int	     height,
+			gboolean     root)
+{
+	return xfce_bg_create_surface_scale (bg,
+					     window,
+					     width,
+					     height,
+					     1,
+					     root);
+}
+
+/**
+ * xfce_bg_create_surface:
+ * @bg: XfceBG
+ * @window:
+ * @width:
+ * @height:
+ * @scale:
+ * @root:
+ *
+ * Create a scaled surface that can be set as background for @window. If @is_root is
+ * TRUE, the surface created will be created by a temporary X server connection
+ * so that if someone calls XKillClient on it, it won't affect the application
+ * who created it.
+ **/
+cairo_surface_t *
+xfce_bg_create_surface_scale (XfceBG      *bg,
+			      GdkWindow   *window,
+			      int          width,
+			      int          height,
+			      int          scale,
+			      gboolean     root)
+{
+	int pm_width, pm_height;
+
+	cairo_surface_t *surface;
+	cairo_t *cr;
+
+	g_return_val_if_fail (bg != NULL, NULL);
+	g_return_val_if_fail (window != NULL, NULL);
+
+	if (bg->pixbuf_cache &&
+	    (gdk_pixbuf_get_width (bg->pixbuf_cache) != width ||
+	     gdk_pixbuf_get_height (bg->pixbuf_cache) != height))
+	{
+		g_object_unref (bg->pixbuf_cache);
+		bg->pixbuf_cache = NULL;
+	}
+
+	xfce_bg_get_pixmap_size (bg, width, height, &pm_width, &pm_height);
+
+	if (root)
+	{
+		surface = make_root_pixmap (window, pm_width * scale, pm_height * scale);
+	}
+	else
+	{
+		surface = gdk_window_create_similar_surface (window, CAIRO_CONTENT_COLOR,
+							     pm_width, pm_height);
+	}
+
+	cr = cairo_create (surface);
+	cairo_scale (cr, (double)scale, (double)scale);
+
+	if (!bg->filename && bg->color_type == XFCE_BG_COLOR_SOLID) {
+		gdk_cairo_set_source_rgba (cr, &(bg->primary));
+	}
+	else
+	{
+		GdkPixbuf *pixbuf;
+
+		pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
+					 width, height);
+		xfce_bg_draw (bg, pixbuf, gdk_window_get_screen (window), root);
+		gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+		g_object_unref (pixbuf);
+	}
+
+	cairo_paint (cr);
+
+	cairo_destroy (cr);
+
+	return surface;
+}
+
+
+/* determine if a background is darker or lighter than average, to help
+ * clients know what colors to draw on top with
+ */
+gboolean
+xfce_bg_is_dark (XfceBG *bg,
+		  int      width,
+		  int      height)
+{
+	GdkRGBA color;
+	int intensity;
+	GdkPixbuf *pixbuf;
+
+	g_return_val_if_fail (bg != NULL, FALSE);
+
+	if (bg->color_type == XFCE_BG_COLOR_SOLID) {
+		color = bg->primary;
+	} else {
+		color.red = (bg->primary.red + bg->secondary.red) / 2;
+		color.green = (bg->primary.green + bg->secondary.green) / 2;
+		color.blue = (bg->primary.blue + bg->secondary.blue) / 2;
+	}
+	pixbuf = get_pixbuf_for_size (bg, -1, width, height);
+	if (pixbuf) {
+		GdkRGBA argb;
+		guchar a, r, g, b;
+
+		pixbuf_average_value (pixbuf, &argb);
+		a = argb.alpha * 0xff;
+		r = argb.red * 0xff;
+		g = argb.green * 0xff;
+		b = argb.blue * 0xff;
+
+		color.red = (color.red * (0xFF - a) + r * 0x101 * a) / 0xFF;
+		color.green = (color.green * (0xFF - a) + g * 0x101 * a) / 0xFF;
+		color.blue = (color.blue * (0xFF - a) + b * 0x101 * a) / 0xFF;
+		g_object_unref (pixbuf);
+	}
+
+	intensity = ((guint) (color.red * 65535) * 77 +
+		     (guint) (color.green * 65535) * 150 +
+		     (guint) (color.blue * 65535) * 28) >> 16;
+
+	return intensity < 160; /* biased slightly to be dark */
+}
+
+/*
+ * Create a persistent pixmap. We create a separate display
+ * and set the closedown mode on it to RetainPermanent.
+ */
+static cairo_surface_t *
+make_root_pixmap (GdkWindow *window, gint width, gint height)
+{
+	GdkScreen *screen = gdk_window_get_screen(window);
+	char *disp_name = DisplayString (GDK_WINDOW_XDISPLAY (window));
+	Display *display;
+	Pixmap xpixmap;
+	cairo_surface_t *surface;
+	int depth;
+
+	/* Desktop background pixmap should be created from dummy X client since most
+	 * applications will try to kill it with XKillClient later when changing pixmap
+	 */
+	display = XOpenDisplay (disp_name);
+
+	if (display == NULL) {
+		g_warning ("Unable to open display '%s' when setting background pixmap\n",
+		           (disp_name) ? disp_name : "NULL");
+		return NULL;
+	}
+
+	depth = DefaultDepth (display, gdk_x11_screen_get_screen_number (screen));
+	xpixmap = XCreatePixmap (display, GDK_WINDOW_XID (window), width, height, depth);
+
+	XFlush (display);
+	XSetCloseDownMode (display, RetainPermanent);
+	XCloseDisplay (display);
+
+	surface = cairo_xlib_surface_create (GDK_SCREEN_XDISPLAY (screen), xpixmap,
+                                             GDK_VISUAL_XVISUAL (gdk_screen_get_system_visual (screen)),
+        				     width, height);
+
+	return surface;
+}
+
+static gboolean
+get_original_size (const char *filename,
+		   int        *orig_width,
+		   int        *orig_height)
+{
+	gboolean result;
+
+        if (gdk_pixbuf_get_file_info (filename, orig_width, orig_height))
+		result = TRUE;
+	else
+		result = FALSE;
+
+	return result;
+}
+
+static const char *
+get_filename_for_size (XfceBG *bg, gint best_width, gint best_height)
+{
+	SlideShow *show;
+	Slide *slide;
+	FileSize *size;
+
+	if (!bg->filename)
+		return NULL;
+
+	show = get_as_slideshow (bg, bg->filename);
+	if (!show) {
+		return bg->filename;
+	}
+
+	slide = get_current_slide (show, NULL);
+	slideshow_unref (show);
+	size = find_best_size (slide->file1, best_width, best_height);
+	return size->file;
+}
+
+gboolean
+xfce_bg_get_image_size (XfceBG	       *bg,
+			 XfceDesktopThumbnailFactory *factory,
+			 int                    best_width,
+			 int                    best_height,
+			 int		       *width,
+			 int		       *height)
+{
+	GdkPixbuf *thumb;
+	gboolean result = FALSE;
+	const gchar *filename;
+
+	g_return_val_if_fail (bg != NULL, FALSE);
+	g_return_val_if_fail (factory != NULL, FALSE);
+
+	if (!bg->filename)
+		return FALSE;
+
+	filename = get_filename_for_size (bg, best_width, best_height);
+	thumb = create_thumbnail_for_filename (factory, filename);
+	if (thumb) {
+		if (get_thumb_annotations (thumb, width, height))
+			result = TRUE;
+
+		g_object_unref (thumb);
+	}
+
+	if (!result) {
+		if (get_original_size (filename, width, height))
+			result = TRUE;
+	}
+
+	return result;
+}
+
+static double
+fit_factor (int from_width, int from_height,
+	    int to_width,   int to_height)
+{
+	return MIN (to_width  / (double) from_width, to_height / (double) from_height);
+}
+
+/**
+ * xfce_bg_create_thumbnail:
+ *
+ * Returns: (transfer full): a #GdkPixbuf showing the background as a thumbnail
+ */
+GdkPixbuf *
+xfce_bg_create_thumbnail (XfceBG               *bg,
+		           XfceDesktopThumbnailFactory *factory,
+			   GdkScreen             *screen,
+			   int                    dest_width,
+			   int                    dest_height)
+{
+	GdkPixbuf *result;
+	GdkPixbuf *thumb;
+
+	g_return_val_if_fail (bg != NULL, NULL);
+
+	result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, dest_width, dest_height);
+
+	draw_color (bg, result);
+
+	if (bg->filename) {
+		thumb = create_img_thumbnail (bg, factory, screen, dest_width, dest_height, -1);
+
+		if (thumb) {
+			draw_image_for_thumb (bg, thumb, result);
+			g_object_unref (thumb);
+		}
+	}
+
+	return result;
+}
+
+/**
+ * xfce_bg_get_surface_from_root:
+ * @screen: a #GdkScreen
+ *
+ * This function queries the _XROOTPMAP_ID property from
+ * the root window associated with @screen to determine
+ * the current root window background surface and returns
+ * a copy of it. If the _XROOTPMAP_ID is not set, then
+ * a black surface is returned.
+ *
+ * Return value: a #cairo_surface_t if successful or %NULL
+ **/
+cairo_surface_t *
+xfce_bg_get_surface_from_root (GdkScreen *screen)
+{
+	int result;
+	gint format;
+	gulong nitems;
+	gulong bytes_after;
+	guchar *data;
+	Atom type;
+	Display *display;
+	int screen_num;
+	cairo_surface_t *surface;
+	cairo_surface_t *source_pixmap;
+	GdkDisplay *gdkdisplay;
+	int width, height;
+	cairo_t *cr;
+
+	display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
+	screen_num = gdk_x11_screen_get_screen_number (screen);
+
+	result = XGetWindowProperty (display,
+				     RootWindow (display, screen_num),
+				     gdk_x11_get_xatom_by_name ("_XROOTPMAP_ID"),
+				     0L, 1L, False, XA_PIXMAP,
+				     &type, &format, &nitems, &bytes_after,
+				     &data);
+	surface = NULL;
+	source_pixmap = NULL;
+
+	if (result != Success || type != XA_PIXMAP ||
+	    format != 32 || nitems != 1) {
+		XFree (data);
+		data = NULL;
+	}
+
+	if (data != NULL) {
+		gdkdisplay = gdk_screen_get_display (screen);
+		gdk_x11_display_error_trap_push (gdkdisplay);
+
+		Pixmap xpixmap = *(Pixmap *) data;
+		Window root_return;
+		int x_ret, y_ret;
+		unsigned int w_ret, h_ret, bw_ret, depth_ret;
+
+		if (XGetGeometry (GDK_SCREEN_XDISPLAY (screen),
+				  xpixmap,
+				  &root_return,
+				  &x_ret, &y_ret, &w_ret, &h_ret, &bw_ret, &depth_ret))
+		{
+			source_pixmap = cairo_xlib_surface_create (GDK_SCREEN_XDISPLAY (screen),
+								   xpixmap,
+								   GDK_VISUAL_XVISUAL (gdk_screen_get_system_visual (screen)),
+								   w_ret, h_ret);
+		}
+
+		gdk_x11_display_error_trap_pop_ignored (gdkdisplay);
+	}
+
+	width = WidthOfScreen (gdk_x11_screen_get_xscreen (screen));
+	height = HeightOfScreen (gdk_x11_screen_get_xscreen (screen));
+
+	if (source_pixmap) {
+		surface = cairo_surface_create_similar (source_pixmap,
+							CAIRO_CONTENT_COLOR,
+							width, height);
+
+		cr = cairo_create (surface);
+		cairo_set_source_surface (cr, source_pixmap, 0, 0);
+		cairo_paint (cr);
+
+		if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
+			cairo_surface_destroy (surface);
+			surface = NULL;
+		}
+
+		cairo_destroy (cr);
+	}
+
+	if (surface == NULL) {
+		surface = gdk_window_create_similar_surface (gdk_screen_get_root_window (screen),
+							     CAIRO_CONTENT_COLOR,
+							     width, height);
+	}
+
+	if (source_pixmap != NULL)
+		cairo_surface_destroy (source_pixmap);
+
+	if (data != NULL)
+		XFree (data);
+
+	return surface;
+}
+
+/* Sets the "ESETROOT_PMAP_ID" property to later be used to free the pixmap,
+ */
+static void
+xfce_bg_set_root_pixmap_id (GdkScreen       *screen,
+			    Display         *display,
+			    Pixmap           xpixmap)
+{
+	Window   xroot   = RootWindow (display, gdk_x11_screen_get_screen_number (screen));
+	char    *atom_names[] = {"_XROOTPMAP_ID", "ESETROOT_PMAP_ID"};
+	Atom     atoms[G_N_ELEMENTS(atom_names)] = {0};
+
+	Atom     type;
+	int      format, result;
+	unsigned long nitems, after;
+	unsigned char *data_root, *data_esetroot;
+	GdkDisplay *gdkdisplay;
+
+	/* Get atoms for both properties in an array, only if they exist.
+	 * This method is to avoid multiple round-trips to Xserver
+	 */
+	if (XInternAtoms (display, atom_names, G_N_ELEMENTS(atom_names), True, atoms) &&
+	    atoms[0] != None && atoms[1] != None) {
+		result = XGetWindowProperty (display, xroot, atoms[0], 0L, 1L,
+		                             False, AnyPropertyType,
+		                             &type, &format, &nitems, &after,
+		                             &data_root);
+
+		if (data_root != NULL && result == Success &&
+		    type == XA_PIXMAP && format == 32 && nitems == 1) {
+			result = XGetWindowProperty (display, xroot, atoms[1],
+			                             0L, 1L, False,
+			                             AnyPropertyType,
+			                             &type, &format, &nitems,
+			                             &after, &data_esetroot);
+
+			if (data_esetroot != NULL && result == Success &&
+			    type == XA_PIXMAP && format == 32 && nitems == 1) {
+				Pixmap xrootpmap = *((Pixmap *) data_root);
+				Pixmap esetrootpmap = *((Pixmap *) data_esetroot);
+
+				gdkdisplay = gdk_screen_get_display (screen);
+				gdk_x11_display_error_trap_push (gdkdisplay);
+				if (xrootpmap && xrootpmap == esetrootpmap) {
+					XKillClient (display, xrootpmap);
+				}
+				if (esetrootpmap && esetrootpmap != xrootpmap) {
+					XKillClient (display, esetrootpmap);
+				}
+				gdk_x11_display_error_trap_pop_ignored (gdkdisplay);
+			}
+			if (data_esetroot != NULL) {
+				XFree (data_esetroot);
+			}
+		}
+		if (data_root != NULL) {
+			XFree (data_root);
+		}
+	}
+
+	/* Get atoms for both properties in an array, create them if needed.
+	 * This method is to avoid multiple round-trips to Xserver
+	 */
+	if (!XInternAtoms (display, atom_names, G_N_ELEMENTS(atom_names), False, atoms) ||
+	    atoms[0] == None || atoms[1] == None) {
+	    g_warning ("Could not create atoms needed to set root pixmap id/properties.\n");
+	    return;
+	}
+
+	/* Set new _XROOTMAP_ID and ESETROOT_PMAP_ID properties */
+	XChangeProperty (display, xroot, atoms[0], XA_PIXMAP, 32,
+			 PropModeReplace, (unsigned char *) &xpixmap, 1);
+
+	XChangeProperty (display, xroot, atoms[1], XA_PIXMAP, 32,
+			 PropModeReplace, (unsigned char *) &xpixmap, 1);
+}
+
+/**
+ * xfce_bg_set_surface_as_root:
+ * @screen: the #GdkScreen to change root background on
+ * @surface: the #cairo_surface_t to set root background from.
+ *   Must be an xlib surface backing a pixmap.
+ *
+ * Set the root pixmap, and properties pointing to it. We
+ * do this atomically with a server grab to make sure that
+ * we won't leak the pixmap if somebody else it setting
+ * it at the same time. (This assumes that they follow the
+ * same conventions we do).  @surface should come from a call
+ * to xfce_bg_create_surface().
+ **/
+void
+xfce_bg_set_surface_as_root (GdkScreen *screen, cairo_surface_t *surface)
+{
+	g_return_if_fail (screen != NULL);
+	g_return_if_fail (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_XLIB);
+
+	/* Desktop background pixmap should be created from dummy X client since most
+	 * applications will try to kill it with XKillClient later when changing pixmap
+	 */
+	Display    *display      = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
+	Pixmap      pixmap_id    = cairo_xlib_surface_get_drawable (surface);
+	Window      xroot        = RootWindow (display, gdk_x11_screen_get_screen_number (screen));
+
+	XGrabServer (display);
+	xfce_bg_set_root_pixmap_id (screen, display, pixmap_id);
+
+	XSetWindowBackgroundPixmap (display, xroot, pixmap_id);
+	XClearWindow (display, xroot);
+
+	XFlush (display);
+	XUngrabServer (display);
+}
+
+/**
+ * xfce_bg_set_surface_as_root_with_crossfade:
+ * @screen: the #GdkScreen to change root background on
+ * @surface: the cairo xlib surface to set root background from
+ *
+ * Set the root pixmap, and properties pointing to it.
+ * This function differs from xfce_bg_set_surface_as_root()
+ * in that it adds a subtle crossfade animation from the
+ * current root pixmap to the new one.
+ *
+ * Return value: (transfer full): a #XfceBGCrossfade object
+ **/
+XfceBGCrossfade *
+xfce_bg_set_surface_as_root_with_crossfade (GdkScreen       *screen,
+		 			    cairo_surface_t *surface)
+{
+	GdkWindow       *root_window;
+	int              width, height;
+	XfceBGCrossfade *fade;
+	cairo_t         *cr;
+	cairo_surface_t *old_surface;
+
+	g_return_val_if_fail (screen != NULL, NULL);
+	g_return_val_if_fail (surface != NULL, NULL);
+
+	root_window = gdk_screen_get_root_window (screen);
+	width       = gdk_window_get_width (root_window);
+	height      = gdk_window_get_height (root_window);
+	fade        = xfce_bg_crossfade_new (width, height);
+	old_surface = xfce_bg_get_surface_from_root (screen);
+
+	xfce_bg_crossfade_set_start_surface (fade, old_surface);
+	xfce_bg_crossfade_set_end_surface (fade, surface);
+
+	/* Before setting the surface as a root pixmap, let's have it draw
+	 * the old stuff, just so it won't be noticable
+	 * (crossfade will later get it back)
+	 */
+	cr = cairo_create (surface);
+	cairo_set_source_surface (cr, old_surface, 0, 0);
+	cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
+	cairo_paint (cr);
+	cairo_destroy (cr);
+	cairo_surface_destroy (old_surface);
+
+	xfce_bg_set_surface_as_root (screen, surface);
+	xfce_bg_crossfade_start (fade, root_window);
+
+	return fade;
+}
+
+/* Implementation of the pixbuf cache */
+struct _SlideShow
+{
+	gint ref_count;
+	double start_time;
+	double total_duration;
+
+	GQueue *slides;
+
+	gboolean has_multiple_sizes;
+
+	/* used during parsing */
+	struct tm start_tm;
+	GQueue *stack;
+};
+
+
+static double
+now (void)
+{
+	GTimeVal tv;
+
+	g_get_current_time (&tv);
+
+	return (double)tv.tv_sec + (tv.tv_usec / 1000000.0);
+}
+
+static Slide *
+get_current_slide (SlideShow *show,
+		   double    *alpha)
+{
+	double delta = fmod (now() - show->start_time, show->total_duration);
+	GList *list;
+	double elapsed;
+	int i;
+
+	if (delta < 0)
+		delta += show->total_duration;
+
+	elapsed = 0;
+	i = 0;
+	for (list = show->slides->head; list != NULL; list = list->next) {
+		Slide *slide = list->data;
+
+		if (elapsed + slide->duration > delta) {
+			if (alpha)
+				*alpha = (delta - elapsed) / (double)slide->duration;
+			return slide;
+		}
+
+		i++;
+		elapsed += slide->duration;
+	}
+
+	/* this should never happen since we have slides and we should always
+	 * find a current slide for the elapsed time since beginning -- we're
+	 * looping with fmod() */
+	g_assert_not_reached ();
+
+	return NULL;
+}
+
+static GdkPixbuf *
+blend (GdkPixbuf *p1,
+       GdkPixbuf *p2,
+       double alpha)
+{
+	GdkPixbuf *result = gdk_pixbuf_copy (p1);
+	GdkPixbuf *tmp;
+
+	if (gdk_pixbuf_get_width (p2) != gdk_pixbuf_get_width (p1) ||
+            gdk_pixbuf_get_height (p2) != gdk_pixbuf_get_height (p1)) {
+		tmp = gdk_pixbuf_scale_simple (p2,
+					       gdk_pixbuf_get_width (p1),
+					       gdk_pixbuf_get_height (p1),
+					       GDK_INTERP_BILINEAR);
+	}
+        else {
+		tmp = g_object_ref (p2);
+	}
+
+	pixbuf_blend (tmp, result, 0, 0, -1, -1, 0, 0, alpha);
+
+        g_object_unref (tmp);
+
+	return result;
+}
+
+typedef	enum {
+	PIXBUF,
+	SLIDESHOW,
+	THUMBNAIL
+} FileType;
+
+struct FileCacheEntry
+{
+	FileType type;
+	char *filename;
+	union {
+		GdkPixbuf *pixbuf;
+		SlideShow *slideshow;
+		GdkPixbuf *thumbnail;
+	} u;
+};
+
+static void
+file_cache_entry_delete (FileCacheEntry *ent)
+{
+	g_free (ent->filename);
+
+	switch (ent->type) {
+	case PIXBUF:
+		g_object_unref (ent->u.pixbuf);
+		break;
+	case SLIDESHOW:
+		slideshow_unref (ent->u.slideshow);
+		break;
+	case THUMBNAIL:
+		g_object_unref (ent->u.thumbnail);
+		break;
+	}
+
+	g_free (ent);
+}
+
+static void
+bound_cache (XfceBG *bg)
+{
+      while (g_list_length (bg->file_cache) >= CACHE_SIZE) {
+	      GList *last_link = g_list_last (bg->file_cache);
+	      FileCacheEntry *ent = last_link->data;
+
+	      file_cache_entry_delete (ent);
+
+	      bg->file_cache = g_list_delete_link (bg->file_cache, last_link);
+      }
+}
+
+static const FileCacheEntry *
+file_cache_lookup (XfceBG *bg, FileType type, const char *filename)
+{
+	GList *list;
+
+	for (list = bg->file_cache; list != NULL; list = list->next) {
+		FileCacheEntry *ent = list->data;
+
+		if (ent && ent->type == type &&
+		    strcmp (ent->filename, filename) == 0) {
+			return ent;
+		}
+	}
+
+	return NULL;
+}
+
+static FileCacheEntry *
+file_cache_entry_new (XfceBG *bg,
+		      FileType type,
+		      const char *filename)
+{
+	FileCacheEntry *ent = g_new0 (FileCacheEntry, 1);
+
+	g_assert (!file_cache_lookup (bg, type, filename));
+
+	ent->type = type;
+	ent->filename = g_strdup (filename);
+
+	bg->file_cache = g_list_prepend (bg->file_cache, ent);
+
+	bound_cache (bg);
+
+	return ent;
+}
+
+static void
+file_cache_add_pixbuf (XfceBG *bg,
+		       const char *filename,
+		       GdkPixbuf *pixbuf)
+{
+	FileCacheEntry *ent = file_cache_entry_new (bg, PIXBUF, filename);
+	ent->u.pixbuf = g_object_ref (pixbuf);
+}
+
+static void
+file_cache_add_thumbnail (XfceBG *bg,
+			  const char *filename,
+			  GdkPixbuf *pixbuf)
+{
+	FileCacheEntry *ent = file_cache_entry_new (bg, THUMBNAIL, filename);
+	ent->u.thumbnail = g_object_ref (pixbuf);
+}
+
+static void
+file_cache_add_slide_show (XfceBG *bg,
+			   const char *filename,
+			   SlideShow *show)
+{
+	FileCacheEntry *ent = file_cache_entry_new (bg, SLIDESHOW, filename);
+	ent->u.slideshow = slideshow_ref (show);
+}
+
+static GdkPixbuf *
+load_from_cache_file (XfceBG     *bg,
+		      const char *filename,
+		      gint        num_monitor,
+		      gint        best_width,
+		      gint        best_height)
+{
+	GdkPixbuf *pixbuf = NULL;
+	gchar *cache_filename;
+
+	cache_filename = get_wallpaper_cache_filename (filename, num_monitor, bg->placement,
+							best_width, best_height);
+
+	if (cache_file_is_valid (filename, cache_filename))
+		pixbuf = gdk_pixbuf_new_from_file (cache_filename, NULL);
+
+	g_free (cache_filename);
+
+	return pixbuf;
+}
+
+static GdkPixbuf *
+get_as_pixbuf_for_size (XfceBG    *bg,
+			const char *filename,
+			gint         monitor,
+			gint         best_width,
+			gint         best_height)
+{
+	const FileCacheEntry *ent;
+	if ((ent = file_cache_lookup (bg, PIXBUF, filename))) {
+		return g_object_ref (ent->u.pixbuf);
+	} else {
+		GdkPixbufFormat *format;
+		GdkPixbuf *pixbuf = NULL;
+		gchar *tmp = NULL;
+		GdkPixbuf *tmp_pixbuf;
+
+		/* Try to hit local cache first if relevant */
+		if (monitor != -1)
+			pixbuf = load_from_cache_file (bg, filename, monitor,
+							best_width, best_height);
+
+		if (!pixbuf) {
+			/* If scalable choose maximum size */
+			format = gdk_pixbuf_get_file_info (filename, NULL, NULL);
+			if (format != NULL)
+				tmp = gdk_pixbuf_format_get_name (format);
+
+			if (g_strcmp0 (tmp, "svg") == 0 &&
+			    (best_width > 0 && best_height > 0) &&
+			    (bg->placement == XFCE_BG_PLACEMENT_FILL_SCREEN ||
+			     bg->placement == XFCE_BG_PLACEMENT_SCALED ||
+			     bg->placement == XFCE_BG_PLACEMENT_ZOOMED))
+			{
+				pixbuf = gdk_pixbuf_new_from_file_at_size (filename,
+									   best_width,
+									   best_height, NULL);
+			} else {
+				pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+			}
+
+			if (tmp != NULL)
+				g_free (tmp);
+		}
+
+		if (pixbuf) {
+			tmp_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+			g_object_unref (pixbuf);
+			pixbuf = tmp_pixbuf;
+			file_cache_add_pixbuf (bg, filename, pixbuf);
+		}
+
+		return pixbuf;
+	}
+}
+
+static SlideShow *
+get_as_slideshow (XfceBG *bg, const char *filename)
+{
+	const FileCacheEntry *ent;
+	if ((ent = file_cache_lookup (bg, SLIDESHOW, filename))) {
+		return slideshow_ref (ent->u.slideshow);
+	}
+	else {
+		SlideShow *show = read_slideshow_file (filename, NULL);
+
+		if (show)
+			file_cache_add_slide_show (bg, filename, show);
+
+		return show;
+	}
+}
+
+static GdkPixbuf *
+get_as_thumbnail (XfceBG *bg, XfceDesktopThumbnailFactory *factory, const char *filename)
+{
+	const FileCacheEntry *ent;
+	if ((ent = file_cache_lookup (bg, THUMBNAIL, filename))) {
+		return g_object_ref (ent->u.thumbnail);
+	}
+	else {
+		GdkPixbuf *thumb = create_thumbnail_for_filename (factory, filename);
+
+		if (thumb)
+			file_cache_add_thumbnail (bg, filename, thumb);
+
+		return thumb;
+	}
+}
+
+static gboolean
+blow_expensive_caches (gpointer data)
+{
+	XfceBG *bg = data;
+	GList *list;
+
+	bg->blow_caches_id = 0;
+
+	if (bg->file_cache) {
+		for (list = bg->file_cache; list != NULL; list = list->next) {
+			FileCacheEntry *ent = list->data;
+
+			if (ent->type == PIXBUF) {
+				file_cache_entry_delete (ent);
+				bg->file_cache = g_list_delete_link (bg->file_cache,
+								     list);
+			}
+		}
+	}
+
+	if (bg->pixbuf_cache) {
+		g_object_unref (bg->pixbuf_cache);
+		bg->pixbuf_cache = NULL;
+	}
+
+	return FALSE;
+}
+
+static void
+blow_expensive_caches_in_idle (XfceBG *bg)
+{
+	if (bg->blow_caches_id == 0) {
+		bg->blow_caches_id =
+			g_idle_add (blow_expensive_caches,
+				    bg);
+	}
+}
+
+
+static gboolean
+on_timeout (gpointer data)
+{
+	XfceBG *bg = data;
+
+	bg->timeout_id = 0;
+
+	queue_transitioned (bg);
+
+	return FALSE;
+}
+
+static double
+get_slide_timeout (Slide   *slide)
+{
+	double timeout;
+	if (slide->fixed) {
+		timeout = slide->duration;
+	} else {
+		/* Maybe the number of steps should be configurable? */
+
+		/* In the worst case we will do a fade from 0 to 256, which mean
+		 * we will never use more than 255 steps, however in most cases
+		 * the first and last value are similar and users can't percieve
+		 * changes in pixel values as small as 1/255th. So, lets not waste
+		 * CPU cycles on transitioning to often.
+		 *
+		 * 64 steps is enough for each step to be just detectable in a 16bit
+		 * color mode in the worst case, so we'll use this as an approximation
+		 * of whats detectable.
+		 */
+		timeout = slide->duration / 64.0;
+	}
+	return timeout;
+}
+
+static void
+ensure_timeout (XfceBG *bg,
+		Slide   *slide)
+{
+	if (!bg->timeout_id) {
+		double timeout = get_slide_timeout (slide);
+
+		/* G_MAXUINT means "only one slide" */
+		if (timeout < G_MAXUINT) {
+			bg->timeout_id = g_timeout_add_full (
+				G_PRIORITY_LOW,
+				timeout * 1000, on_timeout, bg, NULL);
+		}
+
+	}
+}
+
+static time_t
+get_mtime (const char *filename)
+{
+	GFile     *file;
+	GFileInfo *info;
+	time_t     mtime;
+
+	mtime = (time_t)-1;
+
+	if (filename) {
+		file = g_file_new_for_path (filename);
+		info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
+					  G_FILE_QUERY_INFO_NONE, NULL, NULL);
+		if (info) {
+			mtime = g_file_info_get_attribute_uint64 (info,
+								  G_FILE_ATTRIBUTE_TIME_MODIFIED);
+			g_object_unref (info);
+		}
+		g_object_unref (file);
+	}
+
+	return mtime;
+}
+
+static GdkPixbuf *
+scale_thumbnail (XfceBGPlacement placement,
+		 const char *filename,
+		 GdkPixbuf *thumb,
+		 GdkScreen *screen,
+		 int	    dest_width,
+		 int	    dest_height)
+{
+	int o_width;
+	int o_height;
+
+	if (placement != XFCE_BG_PLACEMENT_TILED &&
+	    placement != XFCE_BG_PLACEMENT_CENTERED) {
+
+		/* In this case, the pixbuf will be scaled to fit the screen anyway,
+		 * so just return the pixbuf here
+		 */
+		return g_object_ref (thumb);
+	}
+
+	if (get_thumb_annotations (thumb, &o_width, &o_height)		||
+	    (filename && get_original_size (filename, &o_width, &o_height))) {
+
+		int scr_height = HeightOfScreen (gdk_x11_screen_get_xscreen (screen));
+		int scr_width = WidthOfScreen (gdk_x11_screen_get_xscreen (screen));
+		int thumb_width = gdk_pixbuf_get_width (thumb);
+		int thumb_height = gdk_pixbuf_get_height (thumb);
+		double screen_to_dest = fit_factor (scr_width, scr_height,
+						    dest_width, dest_height);
+		double thumb_to_orig  = fit_factor (thumb_width, thumb_height,
+						    o_width, o_height);
+		double f = thumb_to_orig * screen_to_dest;
+		int new_width, new_height;
+
+		new_width = floor (thumb_width * f + 0.5);
+		new_height = floor (thumb_height * f + 0.5);
+
+		if (placement == XFCE_BG_PLACEMENT_TILED) {
+			/* Heuristic to make sure tiles don't become so small that
+			 * they turn into a blur.
+			 *
+			 * This is strictly speaking incorrect, but the resulting
+			 * thumbnail gives a much better idea what the background
+			 * will actually look like.
+			 */
+
+			if ((new_width < 32 || new_height < 32) &&
+			    (new_width < o_width / 4 || new_height < o_height / 4)) {
+				new_width = o_width / 4;
+				new_height = o_height / 4;
+			}
+		}
+
+		thumb = gdk_pixbuf_scale_simple (thumb, new_width, new_height,
+						 GDK_INTERP_BILINEAR);
+	}
+	else
+		g_object_ref (thumb);
+
+	return thumb;
+}
+
+/* frame_num determines which slide to thumbnail.
+ * -1 means 'current slide'.
+ */
+static GdkPixbuf *
+create_img_thumbnail (XfceBG                      *bg,
+		      XfceDesktopThumbnailFactory *factory,
+		      GdkScreen                    *screen,
+		      int                           dest_width,
+		      int                           dest_height,
+		      int                           frame_num)
+{
+	if (bg->filename) {
+		GdkPixbuf *thumb;
+
+		thumb = get_as_thumbnail (bg, factory, bg->filename);
+
+		if (thumb) {
+			GdkPixbuf *result;
+			result = scale_thumbnail (bg->placement,
+						  bg->filename,
+						  thumb,
+						  screen,
+						  dest_width,
+						  dest_height);
+			g_object_unref (thumb);
+			return result;
+		}
+		else {
+			SlideShow *show = get_as_slideshow (bg, bg->filename);
+
+			if (show) {
+				double alpha;
+				Slide *slide;
+
+				if (frame_num == -1)
+					slide = get_current_slide (show, &alpha);
+				else
+					slide = g_queue_peek_nth (show->slides, frame_num);
+
+				if (slide->fixed) {
+					GdkPixbuf *tmp;
+					FileSize *fs;
+					fs = find_best_size (slide->file1, dest_width, dest_height);
+					tmp = get_as_thumbnail (bg, factory, fs->file);
+					if (tmp) {
+						thumb = scale_thumbnail (bg->placement,
+									 fs->file,
+									 tmp,
+									 screen,
+									 dest_width,
+									 dest_height);
+						g_object_unref (tmp);
+					}
+				}
+				else {
+					FileSize *fs1, *fs2;
+					GdkPixbuf *p1, *p2;
+					fs1 = find_best_size (slide->file1, dest_width, dest_height);
+					p1 = get_as_thumbnail (bg, factory, fs1->file);
+
+					fs2 = find_best_size (slide->file2, dest_width, dest_height);
+					p2 = get_as_thumbnail (bg, factory, fs2->file);
+
+					if (p1 && p2) {
+						GdkPixbuf *thumb1, *thumb2;
+
+						thumb1 = scale_thumbnail (bg->placement,
+									  fs1->file,
+									  p1,
+									  screen,
+									  dest_width,
+									  dest_height);
+
+						thumb2 = scale_thumbnail (bg->placement,
+									  fs2->file,
+									  p2,
+									  screen,
+									  dest_width,
+									  dest_height);
+
+						thumb = blend (thumb1, thumb2, alpha);
+
+						g_object_unref (thumb1);
+						g_object_unref (thumb2);
+					}
+					if (p1)
+						g_object_unref (p1);
+					if (p2)
+						g_object_unref (p2);
+				}
+
+				ensure_timeout (bg, slide);
+
+				slideshow_unref (show);
+			}
+		}
+
+		return thumb;
+	}
+
+	return NULL;
+}
+
+/*
+ * Find the FileSize that best matches the given size.
+ * Do two passes; the first pass only considers FileSizes
+ * that are larger than the given size.
+ * We are looking for the image that best matches the aspect ratio.
+ * When two images have the same aspect ratio, prefer the one whose
+ * width is closer to the given width.
+ */
+static FileSize *
+find_best_size (GSList *sizes, gint width, gint height)
+{
+	GSList *s;
+	gdouble a, d, distance;
+	FileSize *best = NULL;
+	gint pass;
+
+	a = width/(gdouble)height;
+	distance = 10000.0;
+
+	for (pass = 0; pass < 2; pass++) {
+		for (s = sizes; s; s = s->next) {
+			FileSize *size = s->data;
+
+			if (pass == 0 && (size->width < width || size->height < height))
+				continue;
+
+			d = fabs (a - size->width/(gdouble)size->height);
+			if (d < distance) {
+				distance = d;
+				best = size;
+			}
+			else if (d == distance) {
+				if (abs (size->width - width) < abs (best->width - width)) {
+					best = size;
+				}
+			}
+		}
+
+		if (best)
+			break;
+	}
+
+	return best;
+}
+
+static GdkPixbuf *
+get_pixbuf_for_size (XfceBG *bg,
+		     gint monitor,
+		     gint best_width,
+		     gint best_height)
+{
+	guint time_until_next_change;
+	gboolean hit_cache = FALSE;
+
+	/* only hit the cache if the aspect ratio matches */
+	if (bg->pixbuf_cache) {
+		int width, height;
+		width = gdk_pixbuf_get_width (bg->pixbuf_cache);
+		height = gdk_pixbuf_get_height (bg->pixbuf_cache);
+		hit_cache = 0.2 > fabs ((best_width / (double)best_height) - (width / (double)height));
+		if (!hit_cache) {
+			g_object_unref (bg->pixbuf_cache);
+			bg->pixbuf_cache = NULL;
+		}
+	}
+
+	if (!hit_cache && bg->filename) {
+		bg->file_mtime = get_mtime (bg->filename);
+
+		bg->pixbuf_cache = get_as_pixbuf_for_size (bg, bg->filename, monitor,
+							   best_width, best_height);
+		time_until_next_change = G_MAXUINT;
+		if (!bg->pixbuf_cache) {
+			SlideShow *show = get_as_slideshow (bg, bg->filename);
+
+			if (show) {
+				double alpha;
+				Slide *slide;
+
+				slideshow_ref (show);
+
+				slide = get_current_slide (show, &alpha);
+				time_until_next_change = (guint)get_slide_timeout (slide);
+				if (slide->fixed) {
+					FileSize *size = find_best_size (slide->file1,
+									 best_width, best_height);
+					bg->pixbuf_cache =
+						get_as_pixbuf_for_size (bg, size->file, monitor,
+									best_width, best_height);
+				} else {
+					FileSize *size;
+					GdkPixbuf *p1, *p2;
+
+					size = find_best_size (slide->file1,
+								best_width, best_height);
+					p1 = get_as_pixbuf_for_size (bg, size->file, monitor,
+								     best_width, best_height);
+
+					size = find_best_size (slide->file2,
+								best_width, best_height);
+					p2 = get_as_pixbuf_for_size (bg, size->file, monitor,
+								     best_width, best_height);
+
+					if (p1 && p2)
+						bg->pixbuf_cache = blend (p1, p2, alpha);
+					if (p1)
+						g_object_unref (p1);
+					if (p2)
+						g_object_unref (p2);
+				}
+
+				ensure_timeout (bg, slide);
+
+				slideshow_unref (show);
+			}
+		}
+
+		/* If the next slideshow step is a long time away then
+		   we blow away the expensive stuff (large pixbufs) from
+		   the cache */
+		if (time_until_next_change > KEEP_EXPENSIVE_CACHE_SECS)
+		    blow_expensive_caches_in_idle (bg);
+	}
+
+	if (bg->pixbuf_cache)
+		g_object_ref (bg->pixbuf_cache);
+
+	return bg->pixbuf_cache;
+}
+
+static gboolean
+is_different (XfceBG    *bg,
+	      const char *filename)
+{
+	if (!filename && bg->filename) {
+		return TRUE;
+	}
+	else if (filename && !bg->filename) {
+		return TRUE;
+	}
+	else if (!filename && !bg->filename) {
+		return FALSE;
+	}
+	else {
+		time_t mtime = get_mtime (filename);
+
+		if (mtime != bg->file_mtime)
+			return TRUE;
+
+		if (strcmp (filename, bg->filename) != 0)
+			return TRUE;
+
+		return FALSE;
+	}
+}
+
+static void
+clear_cache (XfceBG *bg)
+{
+	GList *list;
+
+	if (bg->file_cache) {
+		for (list = bg->file_cache; list != NULL; list = list->next) {
+			FileCacheEntry *ent = list->data;
+
+			file_cache_entry_delete (ent);
+		}
+		g_list_free (bg->file_cache);
+		bg->file_cache = NULL;
+	}
+
+	if (bg->pixbuf_cache) {
+		g_object_unref (bg->pixbuf_cache);
+
+		bg->pixbuf_cache = NULL;
+	}
+
+	if (bg->timeout_id) {
+		g_source_remove (bg->timeout_id);
+
+		bg->timeout_id = 0;
+	}
+}
+
+/* Pixbuf utilities */
+static void
+pixbuf_average_value (GdkPixbuf *pixbuf,
+                      GdkRGBA   *result)
+{
+	guint64 a_total, r_total, g_total, b_total;
+	guint row, column;
+	int row_stride;
+	const guchar *pixels, *p;
+	int r, g, b, a;
+	guint64 dividend;
+	guint width, height;
+	gdouble dd;
+
+	width = gdk_pixbuf_get_width (pixbuf);
+	height = gdk_pixbuf_get_height (pixbuf);
+	row_stride = gdk_pixbuf_get_rowstride (pixbuf);
+	pixels = gdk_pixbuf_get_pixels (pixbuf);
+
+	/* iterate through the pixbuf, counting up each component */
+	a_total = 0;
+	r_total = 0;
+	g_total = 0;
+	b_total = 0;
+
+	if (gdk_pixbuf_get_has_alpha (pixbuf)) {
+		for (row = 0; row < height; row++) {
+			p = pixels + (row * row_stride);
+			for (column = 0; column < width; column++) {
+				r = *p++;
+				g = *p++;
+				b = *p++;
+				a = *p++;
+
+				a_total += a;
+				r_total += r * a;
+				g_total += g * a;
+				b_total += b * a;
+			}
+		}
+		dividend = height * width * 0xFF;
+		a_total *= 0xFF;
+	} else {
+		for (row = 0; row < height; row++) {
+			p = pixels + (row * row_stride);
+			for (column = 0; column < width; column++) {
+				r = *p++;
+				g = *p++;
+				b = *p++;
+
+				r_total += r;
+				g_total += g;
+				b_total += b;
+			}
+		}
+		dividend = height * width;
+		a_total = dividend * 0xFF;
+	}
+
+	dd = dividend * 0xFF;
+	result->alpha = a_total / dd;
+	result->red = r_total / dd;
+	result->green = g_total / dd;
+	result->blue = b_total / dd;
+}
+
+static GdkPixbuf *
+pixbuf_scale_to_fit (GdkPixbuf *src, int max_width, int max_height)
+{
+	double factor;
+	int src_width, src_height;
+	int new_width, new_height;
+
+	src_width = gdk_pixbuf_get_width (src);
+	src_height = gdk_pixbuf_get_height (src);
+
+	factor = MIN (max_width  / (double) src_width, max_height / (double) src_height);
+
+	new_width  = floor (src_width * factor + 0.5);
+	new_height = floor (src_height * factor + 0.5);
+
+	return gdk_pixbuf_scale_simple (src, new_width, new_height, GDK_INTERP_BILINEAR);
+}
+
+static GdkPixbuf *
+pixbuf_scale_to_min (GdkPixbuf *src, int min_width, int min_height)
+{
+	double factor;
+	int src_width, src_height;
+	int new_width, new_height;
+	GdkPixbuf *dest;
+
+	src_width = gdk_pixbuf_get_width (src);
+	src_height = gdk_pixbuf_get_height (src);
+
+	factor = MAX (min_width / (double) src_width, min_height / (double) src_height);
+
+	new_width = floor (src_width * factor + 0.5);
+	new_height = floor (src_height * factor + 0.5);
+
+	dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+			       gdk_pixbuf_get_has_alpha (src),
+			       8, min_width, min_height);
+	if (!dest)
+		return NULL;
+
+	/* crop the result */
+	gdk_pixbuf_scale (src, dest,
+			  0, 0,
+			  min_width, min_height,
+			  (new_width - min_width) / -2,
+			  (new_height - min_height) / -2,
+			  factor,
+			  factor,
+			  GDK_INTERP_BILINEAR);
+	return dest;
+}
+
+static guchar *
+create_gradient (const GdkRGBA *primary,
+		 const GdkRGBA *secondary,
+		 int	         n_pixels)
+{
+	guchar *result = g_malloc (n_pixels * 3);
+	int i;
+
+	for (i = 0; i < n_pixels; ++i) {
+		double ratio = (i + 0.5) / n_pixels;
+
+		result[3 * i + 0] = (guchar) ((primary->red * (1 - ratio) + secondary->red * ratio) * 0x100);
+		result[3 * i + 1] = (guchar) ((primary->green * (1 - ratio) + secondary->green * ratio) * 0x100);
+		result[3 * i + 2] = (guchar) ((primary->blue * (1 - ratio) + secondary->blue * ratio) * 0x100);
+	}
+
+	return result;
+}
+
+static void
+pixbuf_draw_gradient (GdkPixbuf    *pixbuf,
+		      gboolean      horizontal,
+		      GdkRGBA      *primary,
+		      GdkRGBA      *secondary,
+		      GdkRectangle *rect)
+{
+	int width;
+	int height;
+	int rowstride;
+	guchar *dst;
+	int n_channels = 3;
+
+	rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+	width = rect->width;
+	height = rect->height;
+	dst = gdk_pixbuf_get_pixels (pixbuf) + rect->x * n_channels + rowstride * rect->y;
+
+	if (horizontal) {
+		guchar *gradient = create_gradient (primary, secondary, width);
+		int copy_bytes_per_row = width * n_channels;
+		int i;
+
+		for (i = 0; i < height; i++) {
+			guchar *d;
+			d = dst + rowstride * i;
+			memcpy (d, gradient, copy_bytes_per_row);
+		}
+		g_free (gradient);
+	} else {
+		guchar *gb, *gradient;
+		int i;
+
+		gradient = create_gradient (primary, secondary, height);
+		for (i = 0; i < height; i++) {
+			int j;
+			guchar *d;
+
+			d = dst + rowstride * i;
+			gb = gradient + n_channels * i;
+			for (j = width; j > 0; j--) {
+				int k;
+
+				for (k = 0; k < n_channels; k++) {
+					*(d++) = gb[k];
+				}
+			}
+		}
+
+		g_free (gradient);
+	}
+}
+
+static void
+pixbuf_blend (GdkPixbuf *src,
+	      GdkPixbuf *dest,
+	      int	 src_x,
+	      int	 src_y,
+	      int	 src_width,
+	      int        src_height,
+	      int	 dest_x,
+	      int	 dest_y,
+	      double	 alpha)
+{
+	int dest_width = gdk_pixbuf_get_width (dest);
+	int dest_height = gdk_pixbuf_get_height (dest);
+	int offset_x = dest_x - src_x;
+	int offset_y = dest_y - src_y;
+
+	if (src_width < 0)
+		src_width = gdk_pixbuf_get_width (src);
+
+	if (src_height < 0)
+		src_height = gdk_pixbuf_get_height (src);
+
+	if (dest_x < 0)
+		dest_x = 0;
+
+	if (dest_y < 0)
+		dest_y = 0;
+
+	if (dest_x + src_width > dest_width) {
+		src_width = dest_width - dest_x;
+	}
+
+	if (dest_y + src_height > dest_height) {
+		src_height = dest_height - dest_y;
+	}
+
+	gdk_pixbuf_composite (src, dest,
+			      dest_x, dest_y,
+			      src_width, src_height,
+			      offset_x, offset_y,
+			      1, 1, GDK_INTERP_NEAREST,
+			      alpha * 0xFF + 0.5);
+}
+
+static void
+pixbuf_tile (GdkPixbuf *src, GdkPixbuf *dest)
+{
+	int x, y;
+	int tile_width, tile_height;
+	int dest_width = gdk_pixbuf_get_width (dest);
+	int dest_height = gdk_pixbuf_get_height (dest);
+
+	tile_width = gdk_pixbuf_get_width (src);
+	tile_height = gdk_pixbuf_get_height (src);
+
+	for (y = 0; y < dest_height; y += tile_height) {
+		for (x = 0; x < dest_width; x += tile_width) {
+			pixbuf_blend (src, dest, 0, 0,
+				      tile_width, tile_height, x, y, 1.0);
+		}
+	}
+}
+
+static gboolean stack_is (SlideShow *parser, const char *s1, ...);
+
+/* Parser for fading background */
+static void
+handle_start_element (GMarkupParseContext *context,
+		      const gchar         *name,
+		      const gchar        **attr_names,
+		      const gchar        **attr_values,
+		      gpointer             user_data,
+		      GError             **err)
+{
+	SlideShow *parser = user_data;
+	gint i;
+
+	if (strcmp (name, "static") == 0 || strcmp (name, "transition") == 0) {
+		Slide *slide = g_new0 (Slide, 1);
+
+		if (strcmp (name, "static") == 0)
+			slide->fixed = TRUE;
+
+		g_queue_push_tail (parser->slides, slide);
+	}
+	else if (strcmp (name, "size") == 0) {
+		Slide *slide = parser->slides->tail->data;
+		FileSize *size = g_new0 (FileSize, 1);
+		for (i = 0; attr_names[i]; i++) {
+			if (strcmp (attr_names[i], "width") == 0)
+				size->width = atoi (attr_values[i]);
+			else if (strcmp (attr_names[i], "height") == 0)
+				size->height = atoi (attr_values[i]);
+		}
+		if (parser->stack->tail &&
+		    (strcmp (parser->stack->tail->data, "file") == 0 ||
+		     strcmp (parser->stack->tail->data, "from") == 0)) {
+			slide->file1 = g_slist_prepend (slide->file1, size);
+		}
+		else if (parser->stack->tail &&
+			 strcmp (parser->stack->tail->data, "to") == 0) {
+			slide->file2 = g_slist_prepend (slide->file2, size);
+		}
+	}
+	g_queue_push_tail (parser->stack, g_strdup (name));
+}
+
+static void
+handle_end_element (GMarkupParseContext *context,
+		    const gchar         *name,
+		    gpointer             user_data,
+		    GError             **err)
+{
+	SlideShow *parser = user_data;
+
+	g_free (g_queue_pop_tail (parser->stack));
+}
+
+static gboolean
+stack_is (SlideShow *parser,
+	  const char *s1,
+	  ...)
+{
+	GList *stack = NULL;
+	const char *s;
+	GList *l1, *l2;
+	va_list args;
+
+	stack = g_list_prepend (stack, (gpointer)s1);
+
+	va_start (args, s1);
+
+	s = va_arg (args, const char *);
+	while (s) {
+		stack = g_list_prepend (stack, (gpointer)s);
+		s = va_arg (args, const char *);
+	}
+
+	va_end (args);
+
+	l1 = stack;
+	l2 = parser->stack->head;
+
+	while (l1 && l2) {
+		if (strcmp (l1->data, l2->data) != 0) {
+			g_list_free (stack);
+			return FALSE;
+		}
+
+		l1 = l1->next;
+		l2 = l2->next;
+	}
+
+	g_list_free (stack);
+
+	return (!l1 && !l2);
+}
+
+static int
+parse_int (const char *text)
+{
+	return strtol (text, NULL, 0);
+}
+
+static void
+handle_text (GMarkupParseContext *context,
+	     const gchar         *text,
+	     gsize                text_len,
+	     gpointer             user_data,
+	     GError             **err)
+{
+	SlideShow *parser = user_data;
+	FileSize *fs;
+	gint i;
+
+	g_return_if_fail (parser != NULL);
+	g_return_if_fail (parser->slides != NULL);
+
+	Slide *slide = parser->slides->tail ? parser->slides->tail->data : NULL;
+
+	if (stack_is (parser, "year", "starttime", "background", NULL)) {
+		parser->start_tm.tm_year = parse_int (text) - 1900;
+	}
+	else if (stack_is (parser, "month", "starttime", "background", NULL)) {
+		parser->start_tm.tm_mon = parse_int (text) - 1;
+	}
+	else if (stack_is (parser, "day", "starttime", "background", NULL)) {
+		parser->start_tm.tm_mday = parse_int (text);
+	}
+	else if (stack_is (parser, "hour", "starttime", "background", NULL)) {
+		parser->start_tm.tm_hour = parse_int (text) - 1;
+	}
+	else if (stack_is (parser, "minute", "starttime", "background", NULL)) {
+		parser->start_tm.tm_min = parse_int (text);
+	}
+	else if (stack_is (parser, "second", "starttime", "background", NULL)) {
+		parser->start_tm.tm_sec = parse_int (text);
+	}
+	else if (stack_is (parser, "duration", "static", "background", NULL) ||
+		 stack_is (parser, "duration", "transition", "background", NULL)) {
+		g_return_if_fail (slide != NULL);
+
+		slide->duration = g_strtod (text, NULL);
+		parser->total_duration += slide->duration;
+	}
+	else if (stack_is (parser, "file", "static", "background", NULL) ||
+		 stack_is (parser, "from", "transition", "background", NULL)) {
+		g_return_if_fail (slide != NULL);
+
+		for (i = 0; text[i]; i++) {
+			if (!g_ascii_isspace (text[i]))
+				break;
+		}
+		if (text[i] == 0)
+			return;
+		fs = g_new (FileSize, 1);
+		fs->width = -1;
+		fs->height = -1;
+		fs->file = g_strdup (text);
+		slide->file1 = g_slist_prepend (slide->file1, fs);
+		if (slide->file1->next != NULL)
+			parser->has_multiple_sizes = TRUE;
+	}
+	else if (stack_is (parser, "size", "file", "static", "background", NULL) ||
+		 stack_is (parser, "size", "from", "transition", "background", NULL)) {
+		g_return_if_fail (slide != NULL);
+
+		fs = slide->file1->data;
+		fs->file = g_strdup (text);
+		if (slide->file1->next != NULL)
+			parser->has_multiple_sizes = TRUE;
+	}
+	else if (stack_is (parser, "to", "transition", "background", NULL)) {
+		g_return_if_fail (slide != NULL);
+
+		for (i = 0; text[i]; i++) {
+			if (!g_ascii_isspace (text[i]))
+				break;
+		}
+		if (text[i] == 0)
+			return;
+		fs = g_new (FileSize, 1);
+		fs->width = -1;
+		fs->height = -1;
+		fs->file = g_strdup (text);
+		slide->file2 = g_slist_prepend (slide->file2, fs);
+		if (slide->file2->next != NULL)
+			parser->has_multiple_sizes = TRUE;
+	}
+	else if (stack_is (parser, "size", "to", "transition", "background", NULL)) {
+		g_return_if_fail (slide != NULL);
+
+		fs = slide->file2->data;
+		fs->file = g_strdup (text);
+		if (slide->file2->next != NULL)
+			parser->has_multiple_sizes = TRUE;
+	}
+}
+
+static SlideShow *
+slideshow_ref (SlideShow *show)
+{
+	show->ref_count++;
+	return show;
+}
+
+static void
+slideshow_unref (SlideShow *show)
+{
+	GList *list;
+	GSList *slist;
+	FileSize *size;
+
+	show->ref_count--;
+	if (show->ref_count > 0)
+		return;
+
+	for (list = show->slides->head; list != NULL; list = list->next) {
+		Slide *slide = list->data;
+
+		for (slist = slide->file1; slist != NULL; slist = slist->next) {
+			size = slist->data;
+			g_free (size->file);
+			g_free (size);
+		}
+		g_slist_free (slide->file1);
+
+		for (slist = slide->file2; slist != NULL; slist = slist->next) {
+			size = slist->data;
+			g_free (size->file);
+			g_free (size);
+		}
+		g_slist_free (slide->file2);
+
+		g_free (slide);
+	}
+
+	g_queue_free (show->slides);
+
+	g_list_foreach (show->stack->head, (GFunc) g_free, NULL);
+	g_queue_free (show->stack);
+
+	g_free (show);
+}
+
+static void
+dump_bg (SlideShow *show)
+{
+#if 0
+	GList *list;
+	GSList *slist;
+
+	for (list = show->slides->head; list != NULL; list = list->next)
+	{
+		Slide *slide = list->data;
+
+		g_print ("\nSlide: %s\n", slide->fixed? "fixed" : "transition");
+		g_print ("duration: %f\n", slide->duration);
+		g_print ("File1:\n");
+		for (slist = slide->file1; slist != NULL; slist = slist->next) {
+			FileSize *size = slist->data;
+			g_print ("\t%s (%dx%d)\n",
+				 size->file, size->width, size->height);
+		}
+		g_print ("File2:\n");
+		for (slist = slide->file2; slist != NULL; slist = slist->next) {
+			FileSize *size = slist->data;
+			g_print ("\t%s (%dx%d)\n",
+				 size->file, size->width, size->height);
+		}
+	}
+#endif
+}
+
+static void
+threadsafe_localtime (time_t time, struct tm *tm)
+{
+	struct tm *res;
+
+	G_LOCK_DEFINE_STATIC (localtime_mutex);
+
+	G_LOCK (localtime_mutex);
+
+	res = localtime (&time);
+	if (tm) {
+		*tm = *res;
+	}
+
+	G_UNLOCK (localtime_mutex);
+}
+
+static SlideShow *
+read_slideshow_file (const char *filename,
+		     GError     **err)
+{
+	GMarkupParser parser = {
+		handle_start_element,
+		handle_end_element,
+		handle_text,
+		NULL, /* passthrough */
+		NULL, /* error */
+	};
+
+	GFile *file;
+	char *contents = NULL;
+	gsize len;
+	SlideShow *show = NULL;
+	GMarkupParseContext *context = NULL;
+	time_t t;
+
+	if (!filename)
+		return NULL;
+
+	file = g_file_new_for_path (filename);
+	if (!g_file_load_contents (file, NULL, &contents, &len, NULL, NULL)) {
+		g_object_unref (file);
+		return NULL;
+	}
+	g_object_unref (file);
+
+	show = g_new0 (SlideShow, 1);
+	show->ref_count = 1;
+	threadsafe_localtime ((time_t)0, &show->start_tm);
+	show->stack = g_queue_new ();
+	show->slides = g_queue_new ();
+
+	context = g_markup_parse_context_new (&parser, 0, show, NULL);
+
+	if (!g_markup_parse_context_parse (context, contents, len, err)) {
+		slideshow_unref (show);
+		show = NULL;
+	}
+
+
+	if (show) {
+		if (!g_markup_parse_context_end_parse (context, err)) {
+			slideshow_unref (show);
+			show = NULL;
+		}
+	}
+
+	g_markup_parse_context_free (context);
+
+	if (show) {
+		int len;
+
+		t = mktime (&show->start_tm);
+
+		show->start_time = (double)t;
+
+		dump_bg (show);
+
+		len = g_queue_get_length (show->slides);
+
+		/* no slides, that's not a slideshow */
+		if (len == 0) {
+			slideshow_unref (show);
+			show = NULL;
+		/* one slide, there's no transition */
+		} else if (len == 1) {
+			Slide *slide = show->slides->head->data;
+			slide->duration = show->total_duration = G_MAXUINT;
+		}
+	}
+
+	g_free (contents);
+
+	return show;
+}
+
+/* Thumbnail utilities */
+static GdkPixbuf *
+create_thumbnail_for_filename (XfceDesktopThumbnailFactory *factory,
+			       const char            *filename)
+{
+	char *thumb;
+	time_t mtime;
+	GdkPixbuf *orig, *result = NULL;
+	char *uri;
+
+	mtime = get_mtime (filename);
+
+	if (mtime == (time_t)-1)
+		return NULL;
+
+	uri = g_filename_to_uri (filename, NULL, NULL);
+
+	if (uri == NULL)
+		return NULL;
+
+	thumb = xfce_desktop_thumbnail_factory_lookup (factory, uri, mtime);
+
+	if (thumb) {
+		result = gdk_pixbuf_new_from_file (thumb, NULL);
+		g_free (thumb);
+	}
+	else {
+		orig = gdk_pixbuf_new_from_file (filename, NULL);
+		if (orig) {
+			int orig_width = gdk_pixbuf_get_width (orig);
+			int orig_height = gdk_pixbuf_get_height (orig);
+
+			result = pixbuf_scale_to_fit (orig, 128, 128);
+
+			g_object_set_data_full (G_OBJECT (result), "mate-thumbnail-height",
+						g_strdup_printf ("%d", orig_height), g_free);
+			g_object_set_data_full (G_OBJECT (result), "mate-thumbnail-width",
+						g_strdup_printf ("%d", orig_width), g_free);
+
+			g_object_unref (orig);
+
+			xfce_desktop_thumbnail_factory_save_thumbnail (factory, result, uri, mtime);
+		}
+		else {
+			xfce_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, mtime);
+		}
+	}
+
+	g_free (uri);
+
+	return result;
+}
+
+static gboolean
+get_thumb_annotations (GdkPixbuf *thumb,
+		       int	 *orig_width,
+		       int	 *orig_height)
+{
+	char *end;
+	const char *wstr, *hstr;
+
+	wstr = gdk_pixbuf_get_option (thumb, "tEXt::Thumb::Image::Width");
+	hstr = gdk_pixbuf_get_option (thumb, "tEXt::Thumb::Image::Height");
+
+	if (hstr && wstr) {
+		*orig_width = strtol (wstr, &end, 10);
+		if (*end != 0)
+			return FALSE;
+
+		*orig_height = strtol (hstr, &end, 10);
+		if (*end != 0)
+			return FALSE;
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+slideshow_has_multiple_sizes (SlideShow *show)
+{
+	return show->has_multiple_sizes;
+}
+
+/*
+ * Returns whether the background is a slideshow.
+ */
+gboolean
+xfce_bg_changes_with_time (XfceBG *bg)
+{
+	SlideShow *show;
+
+	g_return_val_if_fail (bg != NULL, FALSE);
+
+	if (!bg->filename)
+		return FALSE;
+
+	show = get_as_slideshow (bg, bg->filename);
+	if (show)
+		return g_queue_get_length (show->slides) > 1;
+
+	return FALSE;
+}
+
+/**
+ * xfce_bg_create_frame_thumbnail:
+ *
+ * Creates a thumbnail for a certain frame, where 'frame' is somewhat
+ * vaguely defined as 'suitable point to show while single-stepping
+ * through the slideshow'.
+ *
+ * Returns: (transfer full): the newly created thumbnail or
+ * or NULL if frame_num is out of bounds.
+ */
+GdkPixbuf *
+xfce_bg_create_frame_thumbnail (XfceBG			*bg,
+				 XfceDesktopThumbnailFactory	*factory,
+				 GdkScreen			*screen,
+				 int				 dest_width,
+				 int				 dest_height,
+				 int				 frame_num)
+{
+	SlideShow *show;
+	GdkPixbuf *result;
+	GdkPixbuf *thumb;
+        GList *l;
+        int i, skipped;
+        gboolean found;
+
+	g_return_val_if_fail (bg != NULL, FALSE);
+
+	show = get_as_slideshow (bg, bg->filename);
+
+	if (!show)
+		return NULL;
+
+
+	if (frame_num < 0 || frame_num >= g_queue_get_length (show->slides))
+		return NULL;
+
+	i = 0;
+	skipped = 0;
+	found = FALSE;
+	for (l = show->slides->head; l; l = l->next) {
+		Slide *slide = l->data;
+		if (!slide->fixed) {
+			skipped++;
+			continue;
+		}
+		if (i == frame_num) {
+			found = TRUE;
+			break;
+		}
+		i++;
+	}
+	if (!found)
+		return NULL;
+
+
+	result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, dest_width, dest_height);
+
+	draw_color (bg, result);
+
+	if (bg->filename) {
+		thumb = create_img_thumbnail (bg, factory, screen,
+					      dest_width, dest_height,
+					      frame_num + skipped);
+
+		if (thumb) {
+			draw_image_for_thumb (bg, thumb, result);
+			g_object_unref (thumb);
+		}
+	}
+
+	return result;
+}
+
diff --git a/src/xfce-bg.h b/src/xfce-bg.h
new file mode 100644
index 0000000..3ea9c58
--- /dev/null
+++ b/src/xfce-bg.h
@@ -0,0 +1,168 @@
+/* xfce-bg.h -
+
+   Copyright (C) 2007 Red Hat, Inc.
+   Copyright (C) 2012 Jasmine Hassan <jasmine.aura at gmail.com>
+
+   This file is part of the Mate Library.
+
+   The Mate Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Mate 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Mate Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+   Boston, MA  02110-1301, USA.
+
+   Authors: Soren Sandmann <sandmann at redhat.com>
+	    Jasmine Hassan <jasmine.aura at gmail.com>
+*/
+
+#ifndef __XFCE_BG_H__
+#define __XFCE_BG_H__
+
+#include <glib.h>
+#include <gdk/gdk.h>
+#include <gio/gio.h>
+#include "xfce-desktop-thumbnail.h"
+#include "xfce-bg-crossfade.h"
+
+G_BEGIN_DECLS
+
+#define XFCE_TYPE_BG            (xfce_bg_get_type ())
+#define XFCE_BG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), XFCE_TYPE_BG, XfceBG))
+#define XFCE_BG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  XFCE_TYPE_BG, XfceBGClass))
+#define XFCE_IS_BG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), XFCE_TYPE_BG))
+#define XFCE_IS_BG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  XFCE_TYPE_BG))
+#define XFCE_BG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  XFCE_TYPE_BG, XfceBGClass))
+
+#define XFCE_BG_SCHEMA "org.mate.background"
+
+/* whether to draw the desktop bg */
+#define XFCE_BG_KEY_DRAW_BACKGROUND	"draw-background"
+
+/* whether Caja or mate-settings-daemon draw the desktop */
+#define XFCE_BG_KEY_SHOW_DESKTOP	"show-desktop-icons"
+
+/* whether to fade when changing background (By Caja/m-s-d) */
+#define XFCE_BG_KEY_BACKGROUND_FADE	"background-fade"
+
+#define XFCE_BG_KEY_PRIMARY_COLOR	"primary-color"
+#define XFCE_BG_KEY_SECONDARY_COLOR	"secondary-color"
+#define XFCE_BG_KEY_COLOR_TYPE		"color-shading-type"
+#define XFCE_BG_KEY_PICTURE_PLACEMENT	"picture-options"
+#define XFCE_BG_KEY_PICTURE_OPACITY	"picture-opacity"
+#define XFCE_BG_KEY_PICTURE_FILENAME	"picture-filename"
+
+typedef struct _XfceBG XfceBG;
+typedef struct _XfceBGClass XfceBGClass;
+
+typedef enum {
+	XFCE_BG_COLOR_SOLID,
+	XFCE_BG_COLOR_H_GRADIENT,
+	XFCE_BG_COLOR_V_GRADIENT
+} XfceBGColorType;
+
+typedef enum {
+	XFCE_BG_PLACEMENT_TILED,
+	XFCE_BG_PLACEMENT_ZOOMED,
+	XFCE_BG_PLACEMENT_CENTERED,
+	XFCE_BG_PLACEMENT_SCALED,
+	XFCE_BG_PLACEMENT_FILL_SCREEN,
+	XFCE_BG_PLACEMENT_SPANNED
+} XfceBGPlacement;
+
+GType            xfce_bg_get_type              (void);
+XfceBG *         xfce_bg_new                   (void);
+void             xfce_bg_load_from_preferences (XfceBG               *bg);
+void             xfce_bg_load_from_system_preferences  (XfceBG       *bg);
+void             xfce_bg_load_from_system_gsettings    (XfceBG       *bg,
+							GSettings    *settings,
+							gboolean      reset_apply);
+void             xfce_bg_load_from_gsettings   (XfceBG               *bg,
+						GSettings            *settings);
+void             xfce_bg_save_to_preferences   (XfceBG               *bg);
+void             xfce_bg_save_to_gsettings     (XfceBG               *bg,
+						GSettings            *settings);
+
+/* Setters */
+void             xfce_bg_set_filename          (XfceBG               *bg,
+						 const char            *filename);
+void             xfce_bg_set_placement         (XfceBG               *bg,
+						 XfceBGPlacement       placement);
+void             xfce_bg_set_color             (XfceBG               *bg,
+						 XfceBGColorType       type,
+						 GdkRGBA              *primary,
+						 GdkRGBA              *secondary);
+void		 xfce_bg_set_draw_background   (XfceBG		     *bg,
+						gboolean	      draw_background);
+/* Getters */
+gboolean	 xfce_bg_get_draw_background   (XfceBG		     *bg);
+XfceBGPlacement  xfce_bg_get_placement         (XfceBG               *bg);
+void		 xfce_bg_get_color             (XfceBG               *bg,
+						 XfceBGColorType      *type,
+						 GdkRGBA              *primary,
+						 GdkRGBA              *secondary);
+const gchar *    xfce_bg_get_filename          (XfceBG               *bg);
+
+/* Drawing and thumbnailing */
+void             xfce_bg_draw                  (XfceBG               *bg,
+						 GdkPixbuf             *dest,
+						 GdkScreen	       *screen,
+                                                 gboolean               is_root);
+
+cairo_surface_t *xfce_bg_create_surface        (XfceBG               *bg,
+						GdkWindow            *window,
+						int                   width,
+						int                   height,
+						gboolean              root);
+
+cairo_surface_t *xfce_bg_create_surface_scale  (XfceBG               *bg,
+						GdkWindow            *window,
+						int                   width,
+						int                   height,
+						int                   scale,
+						gboolean              root);
+
+gboolean         xfce_bg_get_image_size        (XfceBG               *bg,
+						 XfceDesktopThumbnailFactory *factory,
+                                                 int                    best_width,
+                                                 int                    best_height,
+						 int                   *width,
+						 int                   *height);
+GdkPixbuf *      xfce_bg_create_thumbnail      (XfceBG               *bg,
+						 XfceDesktopThumbnailFactory *factory,
+						 GdkScreen             *screen,
+						 int                    dest_width,
+						 int                    dest_height);
+gboolean         xfce_bg_is_dark               (XfceBG               *bg,
+                                                 int                    dest_width,
+						 int                    dest_height);
+gboolean         xfce_bg_has_multiple_sizes    (XfceBG               *bg);
+gboolean         xfce_bg_changes_with_time     (XfceBG               *bg);
+GdkPixbuf *      xfce_bg_create_frame_thumbnail (XfceBG              *bg,
+						 XfceDesktopThumbnailFactory *factory,
+						 GdkScreen             *screen,
+						 int                    dest_width,
+						 int                    dest_height,
+						 int                    frame_num);
+
+/* Set a surface as root - not a XfceBG method. At some point
+ * if we decide to stabilize the API then we may want to make
+ * these object methods, drop xfce_bg_create_surface, etc.
+ */
+void             xfce_bg_set_surface_as_root   (GdkScreen            *screen,
+						cairo_surface_t    *surface);
+XfceBGCrossfade *xfce_bg_set_surface_as_root_with_crossfade (GdkScreen       *screen,
+							     cairo_surface_t *surface);
+cairo_surface_t *xfce_bg_get_surface_from_root (GdkScreen *screen);
+
+G_END_DECLS
+
+#endif
diff --git a/src/xfce-desktop-thumbnail.c b/src/xfce-desktop-thumbnail.c
new file mode 100644
index 0000000..a214961
--- /dev/null
+++ b/src/xfce-desktop-thumbnail.c
@@ -0,0 +1,1548 @@
+/*
+ * mate-thumbnail.c: Utilities for handling thumbnails
+ *
+ * Copyright (C) 2002 Red Hat, Inc.
+ * Copyright (C) 2010 Carlos Garcia Campos <carlosgc at gnome.org>
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Mate 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Mate Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Alexander Larsson <alexl at redhat.com>
+ */
+
+#include <config.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <time.h>
+#include <math.h>
+#include <string.h>
+#include <glib.h>
+#include <stdio.h>
+
+#define GDK_PIXBUF_ENABLE_BACKEND
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+#include "xfce-desktop-thumbnail.h"
+#include <glib/gstdio.h>
+#include <glib-unix.h>
+
+#define SECONDS_BETWEEN_STATS 10
+
+struct _XfceDesktopThumbnailFactoryPrivate {
+  XfceDesktopThumbnailSize size;
+
+  GMutex lock;
+
+  GList *thumbnailers;
+  GHashTable *mime_types_map;
+  GList *monitors;
+
+  GSettings *settings;
+  gboolean loaded : 1;
+  gboolean disabled : 1;
+  gchar **disabled_types;
+};
+
+static const char *appname = "mate-thumbnail-factory";
+
+static void xfce_desktop_thumbnail_factory_init          (XfceDesktopThumbnailFactory      *factory);
+static void xfce_desktop_thumbnail_factory_class_init    (XfceDesktopThumbnailFactoryClass *class);
+
+G_DEFINE_TYPE (XfceDesktopThumbnailFactory,
+	       xfce_desktop_thumbnail_factory,
+	       G_TYPE_OBJECT)
+#define parent_class xfce_desktop_thumbnail_factory_parent_class
+
+#define XFCE_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE(object) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((object), XFCE_DESKTOP_TYPE_THUMBNAIL_FACTORY, XfceDesktopThumbnailFactoryPrivate))
+
+typedef struct {
+    gint width;
+    gint height;
+    gint input_width;
+    gint input_height;
+    gboolean preserve_aspect_ratio;
+} SizePrepareContext;
+
+#define LOAD_BUFFER_SIZE 4096
+
+#define THUMBNAILER_ENTRY_GROUP "Thumbnailer Entry"
+#define THUMBNAILER_EXTENSION   ".thumbnailer"
+
+typedef struct {
+    volatile gint ref_count;
+
+    gchar *path;
+
+    gchar  *try_exec;
+    gchar  *command;
+    gchar **mime_types;
+} Thumbnailer;
+
+static Thumbnailer *
+thumbnailer_ref (Thumbnailer *thumb)
+{
+  g_return_val_if_fail (thumb != NULL, NULL);
+  g_return_val_if_fail (thumb->ref_count > 0, NULL);
+
+  g_atomic_int_inc (&thumb->ref_count);
+  return thumb;
+}
+
+static void
+thumbnailer_unref (Thumbnailer *thumb)
+{
+  g_return_if_fail (thumb != NULL);
+  g_return_if_fail (thumb->ref_count > 0);
+
+  if (g_atomic_int_dec_and_test (&thumb->ref_count))
+    {
+      g_free (thumb->path);
+      g_free (thumb->try_exec);
+      g_free (thumb->command);
+      g_strfreev (thumb->mime_types);
+
+      g_slice_free (Thumbnailer, thumb);
+    }
+}
+
+static Thumbnailer *
+thumbnailer_load (Thumbnailer *thumb)
+{
+  GKeyFile *key_file;
+  GError *error = NULL;
+
+  key_file = g_key_file_new ();
+  if (!g_key_file_load_from_file (key_file, thumb->path, 0, &error))
+    {
+      g_warning ("Failed to load thumbnailer from \"%s\": %s\n", thumb->path, error->message);
+      g_error_free (error);
+      thumbnailer_unref (thumb);
+      g_key_file_free (key_file);
+
+      return NULL;
+    }
+
+  if (!g_key_file_has_group (key_file, THUMBNAILER_ENTRY_GROUP))
+    {
+      g_warning ("Invalid thumbnailer: missing group \"%s\"\n", THUMBNAILER_ENTRY_GROUP);
+      thumbnailer_unref (thumb);
+      g_key_file_free (key_file);
+
+      return NULL;
+    }
+
+  thumb->command = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "Exec", NULL);
+  if (!thumb->command)
+    {
+      g_warning ("Invalid thumbnailer: missing Exec key\n");
+      thumbnailer_unref (thumb);
+      g_key_file_free (key_file);
+
+      return NULL;
+    }
+
+  thumb->mime_types = g_key_file_get_string_list (key_file, THUMBNAILER_ENTRY_GROUP, "MimeType", NULL, NULL);
+  if (!thumb->mime_types)
+    {
+      g_warning ("Invalid thumbnailer: missing MimeType key\n");
+      thumbnailer_unref (thumb);
+      g_key_file_free (key_file);
+
+      return NULL;
+    }
+
+  thumb->try_exec = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "TryExec", NULL);
+
+  g_key_file_free (key_file);
+
+  return thumb;
+}
+
+static Thumbnailer *
+thumbnailer_reload (Thumbnailer *thumb)
+{
+  g_return_val_if_fail (thumb != NULL, NULL);
+
+  g_free (thumb->command);
+  thumb->command = NULL;
+  g_strfreev (thumb->mime_types);
+  thumb->mime_types = NULL;
+  g_free (thumb->try_exec);
+  thumb->try_exec = NULL;
+
+  return thumbnailer_load (thumb);
+}
+
+static Thumbnailer *
+thumbnailer_new (const gchar *path)
+{
+  Thumbnailer *thumb;
+
+  thumb = g_slice_new0 (Thumbnailer);
+  thumb->ref_count = 1;
+  thumb->path = g_strdup (path);
+
+  return thumbnailer_load (thumb);
+}
+
+static gboolean
+thumbnailer_try_exec (Thumbnailer *thumb)
+{
+  gchar *path;
+  gboolean retval;
+
+  if (G_UNLIKELY (!thumb))
+    return FALSE;
+
+  /* TryExec is optinal, but Exec isn't, so we assume
+   * the thumbnailer can be run when TryExec is not present
+   */
+  if (!thumb->try_exec)
+    return TRUE;
+
+  path = g_find_program_in_path (thumb->try_exec);
+  retval = path != NULL;
+  g_free (path);
+
+  return retval;
+}
+
+static gpointer
+init_thumbnailers_dirs (gpointer data)
+{
+  const gchar * const *data_dirs;
+  gchar **thumbs_dirs;
+  guint i, length;
+
+  data_dirs = g_get_system_data_dirs ();
+  length = g_strv_length ((char **) data_dirs);
+
+  thumbs_dirs = g_new (gchar *, length + 2);
+  thumbs_dirs[0] = g_build_filename (g_get_user_data_dir (), "thumbnailers", NULL);
+  for (i = 0; i < length; i++)
+    thumbs_dirs[i + 1] = g_build_filename (data_dirs[i], "thumbnailers", NULL);
+  thumbs_dirs[length + 1] = NULL;
+
+  return thumbs_dirs;
+}
+
+static const gchar * const *
+get_thumbnailers_dirs (void)
+{
+  static GOnce once_init = G_ONCE_INIT;
+  return g_once (&once_init, init_thumbnailers_dirs, NULL);
+}
+
+static void
+size_prepared_cb (GdkPixbufLoader *loader, 
+		  int              width,
+		  int              height,
+		  gpointer         data)
+{
+  SizePrepareContext *info = data;
+  
+  g_return_if_fail (width > 0 && height > 0);
+  
+  info->input_width = width;
+  info->input_height = height;
+  
+  if (width < info->width && height < info->height) return;
+  
+  if (info->preserve_aspect_ratio && 
+      (info->width > 0 || info->height > 0)) {
+    if (info->width < 0)
+      {
+	width = width * (double)info->height/(double)height;
+	height = info->height;
+      }
+    else if (info->height < 0)
+      {
+	height = height * (double)info->width/(double)width;
+	width = info->width;
+      }
+    else if ((double)height * (double)info->width >
+	     (double)width * (double)info->height) {
+      width = 0.5 + (double)width * (double)info->height / (double)height;
+      height = info->height;
+    } else {
+      height = 0.5 + (double)height * (double)info->width / (double)width;
+      width = info->width;
+    }
+  } else {
+    if (info->width > 0)
+      width = info->width;
+    if (info->height > 0)
+      height = info->height;
+  }
+  
+  gdk_pixbuf_loader_set_size (loader, width, height);
+}
+
+static GdkPixbufLoader *
+create_loader (GFile        *file,
+               const guchar *data,
+               gsize         size)
+{
+  GdkPixbufLoader *loader = NULL;
+  GError *error = NULL;
+  char *mime_type;
+  char *filename;
+
+  /* need to specify the type here because the gdk_pixbuf_loader_write
+     doesn't have access to the filename in order to correct detect
+     the image type. */
+  filename = g_file_get_basename (file);
+  mime_type = g_content_type_guess (filename, data, size, NULL);
+  g_free (filename);
+
+  if (mime_type != NULL) {
+    loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, &error);
+  }
+
+  if (loader == NULL && error != NULL) {
+    g_warning ("Unable to create loader for mime type %s: %s", mime_type, error->message);
+    g_clear_error (&error);
+    loader = gdk_pixbuf_loader_new ();
+  }
+  g_free (mime_type);
+
+  return loader;
+}
+
+static GdkPixbuf *
+_gdk_pixbuf_new_from_uri_at_scale (const char *uri,
+				   gint        width,
+				   gint        height,
+				   gboolean    preserve_aspect_ratio)
+{
+    gboolean result;
+    guchar buffer[LOAD_BUFFER_SIZE];
+    gsize bytes_read;
+    GdkPixbufLoader *loader = NULL;
+    GdkPixbuf *pixbuf;	
+    GdkPixbufAnimation *animation;
+    GdkPixbufAnimationIter *iter;
+    gboolean has_frame;
+    SizePrepareContext info;
+    GFile *file;
+    GFileInfo *file_info;
+    GInputStream *input_stream;
+    GError *error = NULL;
+
+    g_return_val_if_fail (uri != NULL, NULL);
+
+    input_stream = NULL;
+
+    file = g_file_new_for_uri (uri);
+
+    /* First see if we can get an input stream via preview::icon  */
+    file_info = g_file_query_info (file,
+                                   G_FILE_ATTRIBUTE_PREVIEW_ICON,
+                                   G_FILE_QUERY_INFO_NONE,
+                                   NULL,  /* GCancellable */
+                                   NULL); /* return location for GError */
+    if (file_info != NULL) {
+        GObject *object;
+
+        object = g_file_info_get_attribute_object (file_info,
+                                                   G_FILE_ATTRIBUTE_PREVIEW_ICON);
+        if (object != NULL && G_IS_LOADABLE_ICON (object)) {
+            input_stream = g_loadable_icon_load (G_LOADABLE_ICON (object),
+                                                 0,     /* size */
+                                                 NULL,  /* return location for type */
+                                                 NULL,  /* GCancellable */
+                                                 NULL); /* return location for GError */
+        }
+        g_object_unref (file_info);
+    }
+
+    if (input_stream == NULL) {
+        input_stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
+        if (input_stream == NULL) {
+            g_object_unref (file);
+            return NULL;
+        }
+    }
+
+    has_frame = FALSE;
+
+    result = FALSE;
+    while (!has_frame) {
+
+	bytes_read = g_input_stream_read (input_stream,
+					  buffer,
+					  sizeof (buffer),
+					  NULL,
+					  &error);
+        if (error != NULL) {
+            g_warning ("Error reading from %s: %s", uri, error->message);
+            g_clear_error (&error);
+        }
+	if (bytes_read == -1) {
+	    break;
+	}
+	result = TRUE;
+	if (bytes_read == 0) {
+	    break;
+	}
+
+        if (loader == NULL) {
+            loader = create_loader (file, buffer, bytes_read);
+            if (1 <= width || 1 <= height) {
+              info.width = width;
+              info.height = height;
+              info.input_width = info.input_height = 0;
+              info.preserve_aspect_ratio = preserve_aspect_ratio;
+              g_signal_connect (loader, "size-prepared", G_CALLBACK (size_prepared_cb), &info);
+            }
+            g_assert (loader != NULL);
+        }
+
+	if (!gdk_pixbuf_loader_write (loader,
+				      (unsigned char *)buffer,
+				      bytes_read,
+				      &error)) {
+            g_warning ("Error creating thumbnail for %s: %s", uri, error->message);
+            g_clear_error (&error);
+	    result = FALSE;
+	    break;
+	}
+
+	animation = gdk_pixbuf_loader_get_animation (loader);
+	if (animation) {
+		iter = gdk_pixbuf_animation_get_iter (animation, NULL);
+		if (!gdk_pixbuf_animation_iter_on_currently_loading_frame (iter)) {
+			has_frame = TRUE;
+		}
+		g_object_unref (iter);
+	}
+    }
+
+    gdk_pixbuf_loader_close (loader, NULL);
+
+    if (!result) {
+	g_object_unref (G_OBJECT (loader));
+	g_input_stream_close (input_stream, NULL, NULL);
+	g_object_unref (input_stream);
+	g_object_unref (file);
+	return NULL;
+    }
+
+    g_input_stream_close (input_stream, NULL, NULL);
+    g_object_unref (input_stream);
+    g_object_unref (file);
+
+    pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+    if (pixbuf != NULL) {
+	g_object_ref (G_OBJECT (pixbuf));
+	g_object_set_data (G_OBJECT (pixbuf), "mate-original-width",
+			   GINT_TO_POINTER (info.input_width));
+	g_object_set_data (G_OBJECT (pixbuf), "mate-original-height",
+			   GINT_TO_POINTER (info.input_height));
+    }
+    g_object_unref (G_OBJECT (loader));
+
+    return pixbuf;
+}
+
+static void
+xfce_desktop_thumbnail_factory_finalize (GObject *object)
+{
+  XfceDesktopThumbnailFactory *factory;
+  XfceDesktopThumbnailFactoryPrivate *priv;
+  
+  factory = XFCE_DESKTOP_THUMBNAIL_FACTORY (object);
+
+  priv = factory->priv;
+
+  if (priv->thumbnailers)
+    {
+      g_list_free_full (priv->thumbnailers, (GDestroyNotify)thumbnailer_unref);
+      priv->thumbnailers = NULL;
+    }
+
+  if (priv->mime_types_map)
+    {
+      g_hash_table_destroy (priv->mime_types_map);
+      priv->mime_types_map = NULL;
+    }
+
+  if (priv->monitors)
+    {
+      g_list_free_full (priv->monitors, (GDestroyNotify)g_object_unref);
+      priv->monitors = NULL;
+    }
+
+  g_mutex_clear (&priv->lock);
+
+  if (priv->disabled_types)
+    {
+      g_strfreev (priv->disabled_types);
+      priv->disabled_types = NULL;
+    }
+
+  if (priv->settings)
+    {
+      g_object_unref (priv->settings);
+      priv->settings = NULL;
+    }
+
+  if (G_OBJECT_CLASS (parent_class)->finalize)
+    (* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+/* These should be called with the lock held */
+static void
+xfce_desktop_thumbnail_factory_register_mime_types (XfceDesktopThumbnailFactory *factory,
+                                                     Thumbnailer                  *thumb)
+{
+  XfceDesktopThumbnailFactoryPrivate *priv = factory->priv;
+  gint i;
+
+  for (i = 0; thumb->mime_types[i]; i++)
+    {
+      if (!g_hash_table_lookup (priv->mime_types_map, thumb->mime_types[i]))
+        g_hash_table_insert (priv->mime_types_map,
+                             g_strdup (thumb->mime_types[i]),
+                             thumbnailer_ref (thumb));
+    }
+}
+
+static void
+xfce_desktop_thumbnail_factory_add_thumbnailer (XfceDesktopThumbnailFactory *factory,
+                                                 Thumbnailer                  *thumb)
+{
+  XfceDesktopThumbnailFactoryPrivate *priv = factory->priv;
+
+  xfce_desktop_thumbnail_factory_register_mime_types (factory, thumb);
+  priv->thumbnailers = g_list_prepend (priv->thumbnailers, thumb);
+}
+
+static gboolean
+xfce_desktop_thumbnail_factory_is_disabled (XfceDesktopThumbnailFactory *factory,
+                                             const gchar                  *mime_type)
+{
+  XfceDesktopThumbnailFactoryPrivate *priv = factory->priv;
+  guint i;
+
+  if (priv->disabled)
+    return TRUE;
+
+  if (!priv->disabled_types)
+    return FALSE;
+
+  for (i = 0; priv->disabled_types[i]; i++)
+    {
+      if (g_strcmp0 (priv->disabled_types[i], mime_type) == 0)
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+remove_thumbnailer_from_mime_type_map (gchar       *key,
+                                       Thumbnailer *value,
+                                       gchar       *path)
+{
+  return (strcmp (value->path, path) == 0);
+}
+
+
+static void
+update_or_create_thumbnailer (XfceDesktopThumbnailFactory *factory,
+                              const gchar                  *path)
+{
+  XfceDesktopThumbnailFactoryPrivate *priv = factory->priv;
+  GList *l;
+  Thumbnailer *thumb;
+  gboolean found = FALSE;
+
+  g_mutex_lock (&priv->lock);
+
+  for (l = priv->thumbnailers; l && !found; l = g_list_next (l))
+    {
+      thumb = (Thumbnailer *)l->data;
+
+      if (strcmp (thumb->path, path) == 0)
+        {
+          found = TRUE;
+
+          /* First remove the mime_types associated to this thumbnailer */
+          g_hash_table_foreach_remove (priv->mime_types_map,
+                                       (GHRFunc)remove_thumbnailer_from_mime_type_map,
+                                       (gpointer)path);
+          if (!thumbnailer_reload (thumb))
+              priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
+          else
+              xfce_desktop_thumbnail_factory_register_mime_types (factory, thumb);
+        }
+    }
+
+  if (!found)
+    {
+      thumb = thumbnailer_new (path);
+      if (thumb)
+        xfce_desktop_thumbnail_factory_add_thumbnailer (factory, thumb);
+    }
+
+  g_mutex_unlock (&priv->lock);
+}
+
+static void
+remove_thumbnailer (XfceDesktopThumbnailFactory *factory,
+                    const gchar                  *path)
+{
+  XfceDesktopThumbnailFactoryPrivate *priv = factory->priv;
+  GList *l;
+  Thumbnailer *thumb;
+
+  g_mutex_lock (&priv->lock);
+
+  for (l = priv->thumbnailers; l; l = g_list_next (l))
+    {
+      thumb = (Thumbnailer *)l->data;
+
+      if (strcmp (thumb->path, path) == 0)
+        {
+          priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
+          g_hash_table_foreach_remove (priv->mime_types_map,
+                                       (GHRFunc)remove_thumbnailer_from_mime_type_map,
+                                       (gpointer)path);
+          thumbnailer_unref (thumb);
+
+          break;
+        }
+    }
+
+  g_mutex_unlock (&priv->lock);
+}
+
+static void
+thumbnailers_directory_changed (GFileMonitor                 *monitor,
+                                GFile                        *file,
+                                GFile                        *other_file,
+                                GFileMonitorEvent             event_type,
+                                XfceDesktopThumbnailFactory *factory)
+{
+  gchar *path;
+
+  switch (event_type)
+    {
+    case G_FILE_MONITOR_EVENT_CREATED:
+    case G_FILE_MONITOR_EVENT_CHANGED:
+    case G_FILE_MONITOR_EVENT_DELETED:
+      path = g_file_get_path (file);
+      if (!g_str_has_suffix (path, THUMBNAILER_EXTENSION))
+        {
+          g_free (path);
+          return;
+        }
+
+      if (event_type == G_FILE_MONITOR_EVENT_DELETED)
+        remove_thumbnailer (factory, path);
+      else
+        update_or_create_thumbnailer (factory, path);
+
+      g_free (path);
+      break;
+    default:
+      break;
+    }
+}
+
+static void
+xfce_desktop_thumbnail_factory_load_thumbnailers (XfceDesktopThumbnailFactory *factory)
+{
+  XfceDesktopThumbnailFactoryPrivate *priv = factory->priv;
+  const gchar * const *dirs;
+  guint i;
+
+  if (priv->loaded)
+    return;
+
+  dirs = get_thumbnailers_dirs ();
+  for (i = 0; dirs[i]; i++)
+    {
+      const gchar *path = dirs[i];
+      GDir *dir;
+      GFile *dir_file;
+      GFileMonitor *monitor;
+      const gchar *dirent;
+
+      dir = g_dir_open (path, 0, NULL);
+      if (!dir)
+        continue;
+
+      /* Monitor dir */
+      dir_file = g_file_new_for_path (path);
+      monitor = g_file_monitor_directory (dir_file,
+                                          G_FILE_MONITOR_NONE,
+                                          NULL, NULL);
+      if (monitor)
+        {
+          g_signal_connect (monitor, "changed",
+                            G_CALLBACK (thumbnailers_directory_changed),
+                            factory);
+          priv->monitors = g_list_prepend (priv->monitors, monitor);
+        }
+      g_object_unref (dir_file);
+
+      while ((dirent = g_dir_read_name (dir)))
+        {
+          Thumbnailer *thumb;
+          gchar       *filename;
+
+          if (!g_str_has_suffix (dirent, THUMBNAILER_EXTENSION))
+            continue;
+
+          filename = g_build_filename (path, dirent, NULL);
+          thumb = thumbnailer_new (filename);
+          g_free (filename);
+
+          if (thumb)
+            xfce_desktop_thumbnail_factory_add_thumbnailer (factory, thumb);
+        }
+
+      g_dir_close (dir);
+    }
+
+  priv->loaded = TRUE;
+}
+
+static void
+external_thumbnailers_disabled_all_changed_cb (GSettings                    *settings,
+                                               const gchar                  *key,
+                                               XfceDesktopThumbnailFactory *factory)
+{
+  XfceDesktopThumbnailFactoryPrivate *priv = factory->priv;
+
+  g_mutex_lock (&priv->lock);
+
+  priv->disabled = g_settings_get_boolean (priv->settings, "disable-all");
+  if (priv->disabled)
+    {
+      g_strfreev (priv->disabled_types);
+      priv->disabled_types = NULL;
+    }
+  else
+    {
+      priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
+      xfce_desktop_thumbnail_factory_load_thumbnailers (factory);
+    }
+
+  g_mutex_unlock (&priv->lock);
+}
+
+static void
+external_thumbnailers_disabled_changed_cb (GSettings                    *settings,
+                                           const gchar                  *key,
+                                           XfceDesktopThumbnailFactory *factory)
+{
+  XfceDesktopThumbnailFactoryPrivate *priv = factory->priv;
+
+  g_mutex_lock (&priv->lock);
+
+  if (!priv->disabled)
+    {
+      g_strfreev (priv->disabled_types);
+      priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
+    }
+
+  g_mutex_unlock (&priv->lock);
+}
+
+static void
+xfce_desktop_thumbnail_factory_init (XfceDesktopThumbnailFactory *factory)
+{
+  XfceDesktopThumbnailFactoryPrivate *priv;
+  
+  factory->priv = XFCE_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE (factory);
+
+  priv = factory->priv;
+
+  priv->size = XFCE_DESKTOP_THUMBNAIL_SIZE_NORMAL;
+  
+  priv->mime_types_map = g_hash_table_new_full (g_str_hash,
+                                                g_str_equal,
+                                                (GDestroyNotify)g_free,
+                                                (GDestroyNotify)thumbnailer_unref);
+
+  g_mutex_init (&priv->lock);
+
+  priv->settings = g_settings_new ("org.mate.thumbnailers");
+
+  g_signal_connect (priv->settings, "changed::disable-all",
+                    G_CALLBACK (external_thumbnailers_disabled_all_changed_cb),
+                    factory);
+  g_signal_connect (priv->settings, "changed::disable",
+                    G_CALLBACK (external_thumbnailers_disabled_changed_cb),
+                    factory);
+
+  priv->disabled = g_settings_get_boolean (priv->settings, "disable-all");
+
+  if (!priv->disabled)
+    priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
+
+  if (!priv->disabled)
+    xfce_desktop_thumbnail_factory_load_thumbnailers (factory);
+}
+
+static void
+xfce_desktop_thumbnail_factory_class_init (XfceDesktopThumbnailFactoryClass *class)
+{
+  GObjectClass *gobject_class;
+
+  gobject_class = G_OBJECT_CLASS (class);
+	
+  gobject_class->finalize = xfce_desktop_thumbnail_factory_finalize;
+
+  g_type_class_add_private (class, sizeof (XfceDesktopThumbnailFactoryPrivate));
+}
+
+/**
+ * xfce_desktop_thumbnail_factory_new:
+ * @size: The thumbnail size to use
+ *
+ * Creates a new #XfceDesktopThumbnailFactory.
+ *
+ * This function must be called on the main thread.
+ * 
+ * Return value: a new #XfceDesktopThumbnailFactory
+ *
+ * Since: 2.2
+ **/
+XfceDesktopThumbnailFactory *
+xfce_desktop_thumbnail_factory_new (XfceDesktopThumbnailSize size)
+{
+  XfceDesktopThumbnailFactory *factory;
+  
+  factory = g_object_new (XFCE_DESKTOP_TYPE_THUMBNAIL_FACTORY, NULL);
+  
+  factory->priv->size = size;
+  
+  return factory;
+}
+
+/**
+ * xfce_desktop_thumbnail_factory_lookup:
+ * @factory: a #XfceDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mtime: the mtime of the file
+ *
+ * Tries to locate an existing thumbnail for the file specified.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Return value: (transfer full): The absolute path of the thumbnail, or %NULL if none exist.
+ *
+ * Since: 2.2
+ **/
+char *
+xfce_desktop_thumbnail_factory_lookup (XfceDesktopThumbnailFactory *factory,
+					const char            *uri,
+					time_t                 mtime)
+{
+  XfceDesktopThumbnailFactoryPrivate *priv = factory->priv;
+  char *path, *file;
+  GChecksum *checksum;
+  guint8 digest[16];
+  gsize digest_len = sizeof (digest);
+  GdkPixbuf *pixbuf;
+  gboolean res;
+
+  g_return_val_if_fail (uri != NULL, NULL);
+
+  res = FALSE;
+
+  checksum = g_checksum_new (G_CHECKSUM_MD5);
+  g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
+
+  g_checksum_get_digest (checksum, digest, &digest_len);
+  g_assert (digest_len == 16);
+
+  file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
+  
+  path = g_build_filename (g_get_user_cache_dir (),
+                           "thumbnails",
+                           (priv->size == XFCE_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
+                           file,
+                           NULL);
+  g_free (file);
+
+  pixbuf = gdk_pixbuf_new_from_file (path, NULL);
+  if (pixbuf != NULL)
+    {
+      res = xfce_desktop_thumbnail_is_valid (pixbuf, uri, mtime);
+      g_object_unref (pixbuf);
+    }
+
+  g_checksum_free (checksum);
+
+  if (res)
+    return path;
+
+  g_free (path);
+  return FALSE;
+}
+
+/**
+ * xfce_desktop_thumbnail_factory_has_valid_failed_thumbnail:
+ * @factory: a #XfceDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mtime: the mtime of the file
+ *
+ * Tries to locate an failed thumbnail for the file specified. Writing
+ * and looking for failed thumbnails is important to avoid to try to
+ * thumbnail e.g. broken images several times.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Return value: TRUE if there is a failed thumbnail for the file.
+ *
+ * Since: 2.2
+ **/
+gboolean
+xfce_desktop_thumbnail_factory_has_valid_failed_thumbnail (XfceDesktopThumbnailFactory *factory,
+							    const char            *uri,
+							    time_t                 mtime)
+{
+  char *path, *file;
+  GdkPixbuf *pixbuf;
+  gboolean res;
+  GChecksum *checksum;
+  guint8 digest[16];
+  gsize digest_len = sizeof (digest);
+
+  checksum = g_checksum_new (G_CHECKSUM_MD5);
+  g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
+
+  g_checksum_get_digest (checksum, digest, &digest_len);
+  g_assert (digest_len == 16);
+
+  res = FALSE;
+
+  file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
+
+  path = g_build_filename (g_get_user_cache_dir (),
+                           "thumbnails/fail",
+                           appname,
+                           file,
+                           NULL);
+  g_free (file);
+
+  pixbuf = gdk_pixbuf_new_from_file (path, NULL);
+  g_free (path);
+
+  if (pixbuf)
+    {
+      res = xfce_desktop_thumbnail_is_valid (pixbuf, uri, mtime);
+      g_object_unref (pixbuf);
+    }
+
+  g_checksum_free (checksum);
+
+  return res;
+}
+
+/* forbidden/buggy GdkPixbufFormat names */
+const char *forbidden[] = { "tga", "icns", "jpeg2000" };
+
+static gboolean
+type_is_forbidden (const gchar *name)
+{
+    gint i = 0;
+    for (i = 0; i < G_N_ELEMENTS (forbidden); i++) {
+        if (g_strcmp0 (forbidden[i], name) == 0) {
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+static gboolean
+mimetype_supported_by_gdk_pixbuf (const char *mime_type)
+{
+    guint i;
+    static gsize formats_hash = 0;
+    gchar *key;
+    gboolean result;
+
+    if (g_once_init_enter (&formats_hash)) {
+        GSList *formats, *list;
+        GHashTable *hash;
+
+        hash = g_hash_table_new_full (g_str_hash,
+                                      (GEqualFunc) g_content_type_equals,
+                                      g_free, NULL);
+
+        formats = gdk_pixbuf_get_formats ();
+        list = formats;
+
+        while (list) {
+            GdkPixbufFormat *format = list->data;
+            gchar **mime_types;
+
+            if (type_is_forbidden (format->name)) {
+                gdk_pixbuf_format_set_disabled (format, TRUE);
+                list = list->next;
+                continue;
+            }
+
+            mime_types = gdk_pixbuf_format_get_mime_types (format);
+
+            for (i = 0; mime_types[i] != NULL; i++)
+                g_hash_table_insert (hash,
+                                     (gpointer) g_content_type_from_mime_type (mime_types[i]),
+                                     GUINT_TO_POINTER (1));
+
+            g_strfreev (mime_types);
+            list = list->next;
+        }
+
+        g_slist_free (formats);
+
+        g_once_init_leave (&formats_hash, (gsize) hash);
+    }
+
+    key = g_content_type_from_mime_type (mime_type);
+    if (g_hash_table_lookup ((void*)formats_hash, key))
+            result = TRUE;
+    else
+            result = FALSE;
+    g_free (key);
+
+    return result;
+}
+
+/**
+ * xfce_desktop_thumbnail_factory_can_thumbnail:
+ * @factory: a #XfceDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mime_type: the mime type of the file
+ * @mtime: the mtime of the file
+ *
+ * Returns TRUE if this MateIconFactory can (at least try) to thumbnail
+ * this file. Thumbnails or files with failed thumbnails won't be thumbnailed.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Return value: TRUE if the file can be thumbnailed.
+ *
+ * Since: 2.2
+ **/
+gboolean
+xfce_desktop_thumbnail_factory_can_thumbnail (XfceDesktopThumbnailFactory *factory,
+					       const char            *uri,
+					       const char            *mime_type,
+					       time_t                 mtime)
+{
+  gboolean have_script = FALSE;
+
+  /* Don't thumbnail thumbnails */
+  if (uri &&
+      strncmp (uri, "file:/", 6) == 0 &&
+      (strstr (uri, "/.thumbnails/") != NULL ||
+      strstr (uri, "/.cache/thumbnails/") != NULL))
+    return FALSE;
+  
+  if (!mime_type)
+    return FALSE;
+
+  g_mutex_lock (&factory->priv->lock);
+  if (!xfce_desktop_thumbnail_factory_is_disabled (factory, mime_type))
+    {
+      Thumbnailer *thumb;
+
+      thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type);
+      have_script = thumbnailer_try_exec (thumb);
+    }
+  g_mutex_unlock (&factory->priv->lock);
+
+  if (have_script || mimetype_supported_by_gdk_pixbuf (mime_type))
+    {
+      return !xfce_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory,
+                                                                          uri,
+                                                                          mtime);
+    }
+  
+  return FALSE;
+}
+
+static char *
+expand_thumbnailing_script (const char *script,
+			    const int   size, 
+			    const char *inuri,
+			    const char *outfile)
+{
+  GString *str;
+  const char *p, *last;
+  char *localfile, *quoted;
+  gboolean got_in;
+
+  str = g_string_new (NULL);
+  
+  got_in = FALSE;
+  last = script;
+  while ((p = strchr (last, '%')) != NULL)
+    {
+      g_string_append_len (str, last, p - last);
+      p++;
+
+      switch (*p) {
+      case 'u':
+	quoted = g_shell_quote (inuri);
+	g_string_append (str, quoted);
+	g_free (quoted);
+	got_in = TRUE;
+	p++;
+	break;
+      case 'i':
+	localfile = g_filename_from_uri (inuri, NULL, NULL);
+	if (localfile)
+	  {
+	    quoted = g_shell_quote (localfile);
+	    g_string_append (str, quoted);
+	    got_in = TRUE;
+	    g_free (quoted);
+	    g_free (localfile);
+	  }
+	p++;
+	break;
+      case 'o':
+	quoted = g_shell_quote (outfile);
+	g_string_append (str, quoted);
+	g_free (quoted);
+	p++;
+	break;
+      case 's':
+	g_string_append_printf (str, "%d", size);
+	p++;
+	break;
+      case '%':
+	g_string_append_c (str, '%');
+	p++;
+	break;
+      case 0:
+      default:
+	break;
+      }
+      last = p;
+    }
+  g_string_append (str, last);
+
+  if (got_in)
+    return g_string_free (str, FALSE);
+
+  g_string_free (str, TRUE);
+  return NULL;
+}
+
+static gboolean
+make_thumbnail_dirs (XfceDesktopThumbnailFactory *factory)
+{
+  char *thumbnail_dir;
+  char *image_dir;
+  gboolean res;
+
+  res = FALSE;
+
+  thumbnail_dir = g_build_filename (g_get_user_cache_dir (),
+                                    "thumbnails",
+                                    NULL);
+  if (!g_file_test (thumbnail_dir, G_FILE_TEST_IS_DIR))
+    {
+      g_mkdir (thumbnail_dir, 0700);
+      res = TRUE;
+    }
+
+  image_dir = g_build_filename (thumbnail_dir,
+				(factory->priv->size == XFCE_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
+				NULL);
+  if (!g_file_test (image_dir, G_FILE_TEST_IS_DIR))
+    {
+      g_mkdir (image_dir, 0700);
+      res = TRUE;
+    }
+
+  g_free (thumbnail_dir);
+  g_free (image_dir);
+  
+  return res;
+}
+
+static gboolean
+make_thumbnail_fail_dirs (XfceDesktopThumbnailFactory *factory)
+{
+  char *thumbnail_dir;
+  char *fail_dir;
+  char *app_dir;
+  gboolean res;
+
+  res = FALSE;
+
+  thumbnail_dir = g_build_filename (g_get_user_cache_dir (),
+                                    "thumbnails",
+                                    NULL);
+  if (!g_file_test (thumbnail_dir, G_FILE_TEST_IS_DIR))
+    {
+      g_mkdir (thumbnail_dir, 0700);
+      res = TRUE;
+    }
+
+  fail_dir = g_build_filename (thumbnail_dir,
+			       "fail",
+			       NULL);
+  if (!g_file_test (fail_dir, G_FILE_TEST_IS_DIR))
+    {
+      g_mkdir (fail_dir, 0700);
+      res = TRUE;
+    }
+
+  app_dir = g_build_filename (fail_dir,
+			      appname,
+			      NULL);
+  if (!g_file_test (app_dir, G_FILE_TEST_IS_DIR))
+    {
+      g_mkdir (app_dir, 0700);
+      res = TRUE;
+    }
+
+  g_free (thumbnail_dir);
+  g_free (fail_dir);
+  g_free (app_dir);
+  
+  return res;
+}
+
+
+/**
+ * xfce_desktop_thumbnail_factory_save_thumbnail:
+ * @factory: a #XfceDesktopThumbnailFactory
+ * @thumbnail: the thumbnail as a pixbuf 
+ * @uri: the uri of a file
+ * @original_mtime: the modification time of the original file 
+ *
+ * Saves @thumbnail at the right place. If the save fails a
+ * failed thumbnail is written.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Since: 2.2
+ **/
+void
+xfce_desktop_thumbnail_factory_save_thumbnail (XfceDesktopThumbnailFactory *factory,
+						GdkPixbuf             *thumbnail,
+						const char            *uri,
+						time_t                 original_mtime)
+{
+  XfceDesktopThumbnailFactoryPrivate *priv = factory->priv;
+  char *path, *file;
+  char *tmp_path;
+  const char *width, *height;
+  int tmp_fd;
+  char mtime_str[21];
+  gboolean saved_ok;
+  GChecksum *checksum;
+  guint8 digest[16];
+  gsize digest_len = sizeof (digest);
+  GError *error;
+
+  checksum = g_checksum_new (G_CHECKSUM_MD5);
+  g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
+
+  g_checksum_get_digest (checksum, digest, &digest_len);
+  g_assert (digest_len == 16);
+
+  file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
+
+  path = g_build_filename (g_get_user_cache_dir (),
+                           "thumbnails",
+                           (priv->size == XFCE_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
+                           file,
+                           NULL);
+
+  g_free (file);
+
+  g_checksum_free (checksum);
+
+  tmp_path = g_strconcat (path, ".XXXXXX", NULL);
+
+  tmp_fd = g_mkstemp (tmp_path);
+  if (tmp_fd == -1 &&
+      make_thumbnail_dirs (factory))
+    {
+      g_free (tmp_path);
+      tmp_path = g_strconcat (path, ".XXXXXX", NULL);
+      tmp_fd = g_mkstemp (tmp_path);
+    }
+
+  if (tmp_fd == -1)
+    {
+      xfce_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, original_mtime);
+      g_free (tmp_path);
+      g_free (path);
+      return;
+    }
+  close (tmp_fd);
+  
+  g_snprintf (mtime_str, 21, "%ld",  original_mtime);
+  width = gdk_pixbuf_get_option (thumbnail, "tEXt::Thumb::Image::Width");
+  height = gdk_pixbuf_get_option (thumbnail, "tEXt::Thumb::Image::Height");
+
+  error = NULL;
+  if (width != NULL && height != NULL) 
+    saved_ok  = gdk_pixbuf_save (thumbnail,
+				 tmp_path,
+				 "png", &error,
+				 "tEXt::Thumb::Image::Width", width,
+				 "tEXt::Thumb::Image::Height", height,
+				 "tEXt::Thumb::URI", uri,
+				 "tEXt::Thumb::MTime", mtime_str,
+				 "tEXt::Software", "MATE::ThumbnailFactory",
+				 NULL);
+  else
+    saved_ok  = gdk_pixbuf_save (thumbnail,
+				 tmp_path,
+				 "png", &error,
+				 "tEXt::Thumb::URI", uri,
+				 "tEXt::Thumb::MTime", mtime_str,
+				 "tEXt::Software", "MATE::ThumbnailFactory",
+				 NULL);
+    
+
+  if (saved_ok)
+    {
+      g_chmod (tmp_path, 0600);
+      g_rename (tmp_path, path);
+    }
+  else
+    {
+      g_warning ("Failed to create thumbnail %s: %s", tmp_path, error->message);
+      xfce_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, original_mtime);
+      g_unlink (tmp_path);
+      g_clear_error (&error);
+    }
+
+  g_free (path);
+  g_free (tmp_path);
+}
+
+/**
+ * xfce_desktop_thumbnail_factory_create_failed_thumbnail:
+ * @factory: a #XfceDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mtime: the modification time of the file
+ *
+ * Creates a failed thumbnail for the file so that we don't try
+ * to re-thumbnail the file later.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Since: 2.2
+ **/
+void
+xfce_desktop_thumbnail_factory_create_failed_thumbnail (XfceDesktopThumbnailFactory *factory,
+							 const char            *uri,
+							 time_t                 mtime)
+{
+  char *path, *file;
+  char *tmp_path;
+  int tmp_fd;
+  char mtime_str[21];
+  gboolean saved_ok;
+  GdkPixbuf *pixbuf;
+  GChecksum *checksum;
+  guint8 digest[16];
+  gsize digest_len = sizeof (digest);
+
+  checksum = g_checksum_new (G_CHECKSUM_MD5);
+  g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
+
+  g_checksum_get_digest (checksum, digest, &digest_len);
+  g_assert (digest_len == 16);
+
+  file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
+
+  path = g_build_filename (g_get_user_cache_dir (),
+                           "thumbnails/fail",
+                           appname,
+                           file,
+                           NULL);
+  g_free (file);
+
+  g_checksum_free (checksum);
+
+  tmp_path = g_strconcat (path, ".XXXXXX", NULL);
+
+  tmp_fd = g_mkstemp (tmp_path);
+  if (tmp_fd == -1 &&
+      make_thumbnail_fail_dirs (factory))
+    {
+      g_free (tmp_path);
+      tmp_path = g_strconcat (path, ".XXXXXX", NULL);
+      tmp_fd = g_mkstemp (tmp_path);
+    }
+
+  if (tmp_fd == -1)
+    {
+      g_free (tmp_path);
+      g_free (path);
+      return;
+    }
+  close (tmp_fd);
+  
+  g_snprintf (mtime_str, 21, "%ld",  mtime);
+  pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
+  saved_ok  = gdk_pixbuf_save (pixbuf,
+			       tmp_path,
+			       "png", NULL, 
+			       "tEXt::Thumb::URI", uri,
+			       "tEXt::Thumb::MTime", mtime_str,
+			       "tEXt::Software", "MATE::ThumbnailFactory",
+			       NULL);
+  g_object_unref (pixbuf);
+  if (saved_ok)
+    {
+      g_chmod (tmp_path, 0600);
+      g_rename(tmp_path, path);
+    }
+
+  g_free (path);
+  g_free (tmp_path);
+}
+
+/**
+ * xfce_desktop_thumbnail_md5:
+ * @uri: an uri
+ *
+ * Calculates the MD5 checksum of the uri. This can be useful
+ * if you want to manually handle thumbnail files.
+ *
+ * Return value: A string with the MD5 digest of the uri string.
+ *
+ * Since: 2.2
+ * Deprecated: 2.22: Use #GChecksum instead
+ **/
+char *
+xfce_desktop_thumbnail_md5 (const char *uri)
+{
+  return g_compute_checksum_for_data (G_CHECKSUM_MD5,
+                                      (const guchar *) uri,
+                                      strlen (uri));
+}
+
+/**
+ * xfce_desktop_thumbnail_path_for_uri:
+ * @uri: an uri
+ * @size: a thumbnail size
+ *
+ * Returns the filename that a thumbnail of size @size for @uri would have.
+ *
+ * Return value: an absolute filename
+ *
+ * Since: 2.2
+ **/
+char *
+xfce_desktop_thumbnail_path_for_uri (const char         *uri,
+				      XfceDesktopThumbnailSize  size)
+{
+  char *md5;
+  char *file;
+  char *path;
+
+  md5 = xfce_desktop_thumbnail_md5 (uri);
+  file = g_strconcat (md5, ".png", NULL);
+  g_free (md5);
+  
+  path = g_build_filename (g_get_user_cache_dir (),
+                           "thumbnails",
+                           (size == XFCE_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
+                           file,
+                           NULL);
+  g_free (file);
+
+  return path;
+}
+
+/**
+ * xfce_desktop_thumbnail_has_uri:
+ * @pixbuf: an loaded thumbnail pixbuf
+ * @uri: a uri
+ *
+ * Returns whether the thumbnail has the correct uri embedded in the
+ * Thumb::URI option in the png.
+ *
+ * Return value: TRUE if the thumbnail is for @uri
+ *
+ * Since: 2.2
+ **/
+gboolean
+xfce_desktop_thumbnail_has_uri (GdkPixbuf          *pixbuf,
+				 const char         *uri)
+{
+  const char *thumb_uri;
+  
+  thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
+  if (!thumb_uri)
+    return FALSE;
+
+  return strcmp (uri, thumb_uri) == 0;
+}
+
+/**
+ * xfce_desktop_thumbnail_is_valid:
+ * @pixbuf: an loaded thumbnail #GdkPixbuf
+ * @uri: a uri
+ * @mtime: the mtime
+ *
+ * Returns whether the thumbnail has the correct uri and mtime embedded in the
+ * png options.
+ *
+ * Return value: TRUE if the thumbnail has the right @uri and @mtime
+ *
+ * Since: 2.2
+ **/
+gboolean
+xfce_desktop_thumbnail_is_valid (GdkPixbuf          *pixbuf,
+				  const char         *uri,
+				  time_t              mtime)
+{
+  const char *thumb_uri, *thumb_mtime_str;
+  time_t thumb_mtime;
+  
+  thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
+  if (!thumb_uri)
+    return FALSE;
+  if (strcmp (uri, thumb_uri) != 0)
+    return FALSE;
+  
+  thumb_mtime_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::MTime");
+  if (!thumb_mtime_str)
+    return FALSE;
+  thumb_mtime = atol (thumb_mtime_str);
+  if (mtime != thumb_mtime)
+    return FALSE;
+  
+  return TRUE;
+}
diff --git a/src/xfce-desktop-thumbnail.h b/src/xfce-desktop-thumbnail.h
new file mode 100644
index 0000000..d26c26d
--- /dev/null
+++ b/src/xfce-desktop-thumbnail.h
@@ -0,0 +1,99 @@
+/*
+ * mate-thumbnail.h: Utilities for handling thumbnails
+ *
+ * Copyright (C) 2002 Red Hat, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Mate 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Mate Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Alexander Larsson <alexl at redhat.com>
+ */
+
+#ifndef XFCE_DESKTOP_THUMBNAIL_H
+#define XFCE_DESKTOP_THUMBNAIL_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <time.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+  XFCE_DESKTOP_THUMBNAIL_SIZE_NORMAL,
+  XFCE_DESKTOP_THUMBNAIL_SIZE_LARGE
+} XfceDesktopThumbnailSize;
+
+#define XFCE_DESKTOP_TYPE_THUMBNAIL_FACTORY		(xfce_desktop_thumbnail_factory_get_type ())
+#define XFCE_DESKTOP_THUMBNAIL_FACTORY(obj)	(G_TYPE_CHECK_INSTANCE_CAST ((obj), XFCE_DESKTOP_TYPE_THUMBNAIL_FACTORY, XfceDesktopThumbnailFactory))
+#define XFCE_DESKTOP_THUMBNAIL_FACTORY_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), XFCE_DESKTOP_TYPE_THUMBNAIL_FACTORY, XfceDesktopThumbnailFactoryClass))
+#define XFCE_DESKTOP_IS_THUMBNAIL_FACTORY(obj)		(G_TYPE_INSTANCE_CHECK_TYPE ((obj), XFCE_DESKTOP_TYPE_THUMBNAIL_FACTORY))
+#define XFCE_DESKTOP_IS_THUMBNAIL_FACTORY_CLASS(klass)	(G_TYPE_CLASS_CHECK_CLASS_TYPE ((klass), XFCE_DESKTOP_TYPE_THUMBNAIL_FACTORY))
+
+typedef struct _XfceDesktopThumbnailFactory        XfceDesktopThumbnailFactory;
+typedef struct _XfceDesktopThumbnailFactoryClass   XfceDesktopThumbnailFactoryClass;
+typedef struct _XfceDesktopThumbnailFactoryPrivate XfceDesktopThumbnailFactoryPrivate;
+
+struct _XfceDesktopThumbnailFactory {
+	GObject parent;
+
+	XfceDesktopThumbnailFactoryPrivate *priv;
+};
+
+struct _XfceDesktopThumbnailFactoryClass {
+	GObjectClass parent;
+};
+
+GType                  xfce_desktop_thumbnail_factory_get_type (void);
+XfceDesktopThumbnailFactory *xfce_desktop_thumbnail_factory_new      (XfceDesktopThumbnailSize     size);
+
+char *                 xfce_desktop_thumbnail_factory_lookup   (XfceDesktopThumbnailFactory *factory,
+								 const char            *uri,
+								 time_t                 mtime);
+
+gboolean               xfce_desktop_thumbnail_factory_has_valid_failed_thumbnail (XfceDesktopThumbnailFactory *factory,
+										   const char            *uri,
+										   time_t                 mtime);
+gboolean               xfce_desktop_thumbnail_factory_can_thumbnail (XfceDesktopThumbnailFactory *factory,
+								      const char            *uri,
+								      const char            *mime_type,
+								      time_t                 mtime);
+GdkPixbuf *            xfce_desktop_thumbnail_factory_generate_thumbnail (XfceDesktopThumbnailFactory *factory,
+									   const char            *uri,
+									   const char            *mime_type);
+void                   xfce_desktop_thumbnail_factory_save_thumbnail (XfceDesktopThumbnailFactory *factory,
+								       GdkPixbuf             *thumbnail,
+								       const char            *uri,
+								       time_t                 original_mtime);
+void                   xfce_desktop_thumbnail_factory_create_failed_thumbnail (XfceDesktopThumbnailFactory *factory,
+										const char            *uri,
+										time_t                 mtime);
+
+
+/* Thumbnailing utils: */
+gboolean   xfce_desktop_thumbnail_has_uri           (GdkPixbuf          *pixbuf,
+						      const char         *uri);
+gboolean   xfce_desktop_thumbnail_is_valid          (GdkPixbuf          *pixbuf,
+						      const char         *uri,
+						      time_t              mtime);
+char *     xfce_desktop_thumbnail_md5               (const char         *uri);
+char *     xfce_desktop_thumbnail_path_for_uri      (const char         *uri,
+						      XfceDesktopThumbnailSize  size);
+
+G_END_DECLS
+
+#endif /* XFCE_DESKTOP_THUMBNAIL_H */
diff --git a/src/xfce-desktop-utils.c b/src/xfce-desktop-utils.c
new file mode 100644
index 0000000..b5269c3
--- /dev/null
+++ b/src/xfce-desktop-utils.c
@@ -0,0 +1,460 @@
+/* -*- Mode: C; c-set-style: linux indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* xfce-desktop-utils.c - Utilities for the MATE Desktop
+
+   Copyright (C) 1998 Tom Tromey
+   All rights reserved.
+
+   This file is part of the Mate Library.
+
+   The Mate Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Mate 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Mate Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+   Boston, MA 02110-1301, USA.  */
+/*
+  @NOTATION@
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+#include <xfce-desktop-utils.h>
+
+#include "private.h"
+
+static void
+gtk_style_shade (GdkRGBA *a,
+                 GdkRGBA *b,
+                 gdouble  k);
+
+static void
+rgb_to_hls (gdouble *r,
+            gdouble *g,
+            gdouble *b);
+
+static void
+hls_to_rgb (gdouble *h,
+            gdouble *l,
+            gdouble *s);
+
+/**
+ * xfce_desktop_prepend_terminal_to_vector:
+ * @argc: a pointer to the vector size
+ * @argv: a pointer to the vector
+ *
+ * Prepends a terminal (either the one configured as default in the user's
+ * MATE setup, or one of the common xterm emulators) to the passed in vector,
+ * modifying it in the process.  The vector should be allocated with #g_malloc,
+ * as this will #g_free the original vector.  Also all elements must have been
+ * allocated separately.  That is the standard glib/MATE way of doing vectors
+ * however.  If the integer that @argc points to is negative, the size will
+ * first be computed.  Also note that passing in pointers to a vector that is
+ * empty, will just create a new vector for you.
+ **/
+void
+xfce_desktop_prepend_terminal_to_vector (int *argc, char ***argv)
+{
+        char **real_argv;
+        int real_argc;
+        int i, j;
+	char **term_argv = NULL;
+	int term_argc = 0;
+	GSettings *settings;
+
+	gchar *terminal = NULL;
+
+	char **the_argv;
+
+        g_return_if_fail (argc != NULL);
+        g_return_if_fail (argv != NULL);
+
+        _mate_desktop_init_i18n ();
+
+	/* sanity */
+        if(*argv == NULL)
+                *argc = 0;
+
+	the_argv = *argv;
+
+	/* compute size if not given */
+	if (*argc < 0) {
+		for (i = 0; the_argv[i] != NULL; i++)
+			;
+		*argc = i;
+	}
+
+	settings = g_settings_new ("org.mate.applications-terminal");
+	terminal = g_settings_get_string (settings, "exec");
+
+	if (terminal) {
+		gchar *command_line;
+		gchar *exec_flag;
+		exec_flag = g_settings_get_string (settings, "exec-arg");
+
+		if (exec_flag == NULL)
+			command_line = g_strdup (terminal);
+		else
+			command_line = g_strdup_printf ("%s %s", terminal,
+							exec_flag);
+
+		g_shell_parse_argv (command_line,
+				    &term_argc,
+				    &term_argv,
+				    NULL /* error */);
+
+		g_free (command_line);
+		g_free (exec_flag);
+		g_free (terminal);
+	}
+	g_object_unref (settings);
+
+	if (term_argv == NULL) {
+		char *check;
+
+		term_argc = 2;
+		term_argv = g_new0 (char *, 3);
+
+		check = g_find_program_in_path ("mate-terminal");
+		if (check != NULL) {
+			term_argv[0] = check;
+			/* Note that mate-terminal takes -x and
+			 * as -e in mate-terminal is broken we use that. */
+			term_argv[1] = g_strdup ("-x");
+		} else {
+			if (check == NULL)
+				check = g_find_program_in_path ("nxterm");
+			if (check == NULL)
+				check = g_find_program_in_path ("color-xterm");
+			if (check == NULL)
+				check = g_find_program_in_path ("rxvt");
+			if (check == NULL)
+				check = g_find_program_in_path ("xterm");
+			if (check == NULL)
+				check = g_find_program_in_path ("dtterm");
+			if (check == NULL) {
+				g_warning (_("Cannot find a terminal, using "
+					     "xterm, even if it may not work"));
+				check = g_strdup ("xterm");
+			}
+			term_argv[0] = check;
+			term_argv[1] = g_strdup ("-e");
+		}
+	}
+
+        real_argc = term_argc + *argc;
+        real_argv = g_new (char *, real_argc + 1);
+
+        for (i = 0; i < term_argc; i++)
+                real_argv[i] = term_argv[i];
+
+        for (j = 0; j < *argc; j++, i++)
+                real_argv[i] = (char *)the_argv[j];
+
+	real_argv[i] = NULL;
+
+	g_free (*argv);
+	*argv = real_argv;
+	*argc = real_argc;
+
+	/* we use g_free here as we sucked all the inner strings
+	 * out from it into real_argv */
+	g_free (term_argv);
+}
+
+/**
+ * xfce_gdk_spawn_command_line_on_screen:
+ * @screen: a GdkScreen
+ * @command: a command line
+ * @error: return location for errors
+ *
+ * This is a replacement for gdk_spawn_command_line_on_screen, deprecated
+ * in GDK 2.24 and removed in GDK 3.0.
+ *
+ * gdk_spawn_command_line_on_screen is like g_spawn_command_line_async(),
+ * except the child process is spawned in such an environment that on
+ * calling gdk_display_open() it would be returned a GdkDisplay with
+ * screen as the default screen.
+ *
+ * This is useful for applications which wish to launch an application
+ * on a specific screen.
+ *
+ * Returns: TRUE on success, FALSE if error is set.
+ *
+ * Since: 1.7.1
+ **/
+gboolean
+xfce_gdk_spawn_command_line_on_screen (GdkScreen *screen, const gchar *command, GError **error)
+{
+	GAppInfo *appinfo = NULL;
+	GdkAppLaunchContext *context = NULL;
+	gboolean res = FALSE;
+
+	appinfo = g_app_info_create_from_commandline (command, NULL, G_APP_INFO_CREATE_NONE, error);
+
+	if (appinfo) {
+		context = gdk_display_get_app_launch_context (gdk_screen_get_display (screen));
+		res = g_app_info_launch (appinfo, NULL, G_APP_LAUNCH_CONTEXT (context), error);
+		g_object_unref (context);
+		g_object_unref (appinfo);
+	}
+
+	return res;
+}
+
+void
+_mate_desktop_init_i18n (void) {
+	static gboolean initialized = FALSE;
+
+	if (!initialized) {
+		bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
+#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
+		bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+#endif
+		initialized = TRUE;
+	}
+}
+
+/**
+ * gtk_style_shade:
+ * @a:  the starting colour
+ * @b:  [out] the resulting colour
+ * @k:  amount to scale lightness and saturation by
+ *
+ * Takes a colour "a", scales the lightness and saturation by a certain amount,
+ * and sets "b" to the resulting colour.
+ * gtkstyle.c cut-and-pastage.
+ */
+static void
+gtk_style_shade (GdkRGBA *a,
+                 GdkRGBA *b,
+                 gdouble  k)
+{
+	gdouble red;
+	gdouble green;
+	gdouble blue;
+
+	red = a->red;
+	green = a->green;
+	blue = a->blue;
+
+	rgb_to_hls (&red, &green, &blue);
+
+	green *= k;
+	if (green > 1.0)
+		green = 1.0;
+	else if (green < 0.0)
+		green = 0.0;
+
+	blue *= k;
+	if (blue > 1.0)
+		blue = 1.0;
+	else if (blue < 0.0)
+		blue = 0.0;
+
+	hls_to_rgb (&red, &green, &blue);
+
+	b->red = red;
+	b->green = green;
+	b->blue = blue;
+}
+
+/**
+ * rgb_to_hls:
+ * @r:  on input, red; on output, hue
+ * @g:  on input, green; on output, lightness
+ * @b:  on input, blue; on output, saturation
+ *
+ * Converts a red/green/blue triplet to a hue/lightness/saturation triplet.
+ */
+static void
+rgb_to_hls (gdouble *r,
+            gdouble *g,
+            gdouble *b)
+{
+	gdouble min;
+	gdouble max;
+	gdouble red;
+	gdouble green;
+	gdouble blue;
+	gdouble h, l, s;
+	gdouble delta;
+
+	red = *r;
+	green = *g;
+	blue = *b;
+
+	if (red > green)
+	{
+		if (red > blue)
+			max = red;
+		else
+			max = blue;
+
+		if (green < blue)
+			min = green;
+		else
+			min = blue;
+	}
+	else
+	{
+		if (green > blue)
+			max = green;
+		else
+			max = blue;
+
+		if (red < blue)
+			min = red;
+		else
+			min = blue;
+	}
+
+	l = (max + min) / 2;
+	s = 0;
+	h = 0;
+
+	if (max != min)
+	{
+		if (l <= 0.5)
+			s = (max - min) / (max + min);
+		else
+			s = (max - min) / (2 - max - min);
+
+		delta = max -min;
+		if (red == max)
+			h = (green - blue) / delta;
+		else if (green == max)
+			h = 2 + (blue - red) / delta;
+		else if (blue == max)
+			h = 4 + (red - green) / delta;
+
+		h *= 60;
+		if (h < 0.0)
+			h += 360;
+	}
+
+	*r = h;
+	*g = l;
+	*b = s;
+}
+
+/**
+ * hls_to_rgb:
+ * @h: on input, hue; on output, red
+ * @l: on input, lightness; on output, green
+ * @s  on input, saturation; on output, blue
+ *
+ * Converts a hue/lightness/saturation triplet to a red/green/blue triplet.
+ */
+static void
+hls_to_rgb (gdouble *h,
+            gdouble *l,
+            gdouble *s)
+{
+	gdouble hue;
+	gdouble lightness;
+	gdouble saturation;
+	gdouble m1, m2;
+	gdouble r, g, b;
+
+	lightness = *l;
+	saturation = *s;
+
+	if (lightness <= 0.5)
+		m2 = lightness * (1 + saturation);
+	else
+		m2 = lightness + saturation - lightness * saturation;
+	m1 = 2 * lightness - m2;
+
+	if (saturation == 0)
+	{
+		*h = lightness;
+		*l = lightness;
+		*s = lightness;
+	}
+	else
+	{
+		hue = *h + 120;
+		while (hue > 360)
+			hue -= 360;
+		while (hue < 0)
+			hue += 360;
+
+		if (hue < 60)
+			r = m1 + (m2 - m1) * hue / 60;
+		else if (hue < 180)
+			r = m2;
+		else if (hue < 240)
+			r = m1 + (m2 - m1) * (240 - hue) / 60;
+		else
+			r = m1;
+
+		hue = *h;
+		while (hue > 360)
+			hue -= 360;
+		while (hue < 0)
+			hue += 360;
+
+		if (hue < 60)
+			g = m1 + (m2 - m1) * hue / 60;
+		else if (hue < 180)
+			g = m2;
+		else if (hue < 240)
+			g = m1 + (m2 - m1) * (240 - hue) / 60;
+		else
+			g = m1;
+
+		hue = *h - 120;
+		while (hue > 360)
+			hue -= 360;
+		while (hue < 0)
+			hue += 360;
+
+		if (hue < 60)
+			b = m1 + (m2 - m1) * hue / 60;
+		else if (hue < 180)
+			b = m2;
+		else if (hue < 240)
+			b = m1 + (m2 - m1) * (240 - hue) / 60;
+		else
+			b = m1;
+
+		*h = r;
+		*l = g;
+		*s = b;
+	}
+}
+
+/* Based on set_color() in gtkstyle.c */
+#define LIGHTNESS_MULT 1.3
+#define DARKNESS_MULT  0.7
+void
+xfce_desktop_gtk_style_get_light_color (GtkStyleContext *style,
+                                        GtkStateFlags    state,
+                                        GdkRGBA         *color)
+{
+	gtk_style_context_get_background_color (style, state, color);
+	gtk_style_shade (color, color, LIGHTNESS_MULT);
+}
+
+void
+xfce_desktop_gtk_style_get_dark_color (GtkStyleContext *style,
+                                       GtkStateFlags    state,
+                                       GdkRGBA         *color)
+{
+	gtk_style_context_get_background_color (style, state, color);
+	gtk_style_shade (color, color, DARKNESS_MULT);
+}
diff --git a/src/xfce-desktop-utils.h b/src/xfce-desktop-utils.h
new file mode 100644
index 0000000..7ac7394
--- /dev/null
+++ b/src/xfce-desktop-utils.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; c-set-style: linux indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* mate-ditem.h - Utilities for the MATE Desktop
+
+   Copyright (C) 1998 Tom Tromey
+   All rights reserved.
+
+   This file is part of the Mate Library.
+
+   The Mate Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Mate 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Mate Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+   Boston, MA  02110-1301, USA.  */
+/*
+  @NOTATION@
+ */
+
+#ifndef XFCE_DESKTOP_UTILS_H
+#define XFCE_DESKTOP_UTILS_H
+
+#include <glib.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/* prepend the terminal command to a vector */
+void xfce_desktop_prepend_terminal_to_vector (int *argc, char ***argv);
+
+/* replace gdk_spawn_command_line_on_screen, not available in GTK3 */
+gboolean xfce_gdk_spawn_command_line_on_screen (GdkScreen *screen, const gchar *command, GError **error);
+
+void
+xfce_desktop_gtk_style_get_light_color (GtkStyleContext *style,
+                                        GtkStateFlags    state,
+                                        GdkRGBA         *color);
+
+void
+xfce_desktop_gtk_style_get_dark_color (GtkStyleContext *style,
+                                       GtkStateFlags    state,
+                                       GdkRGBA         *color);
+
+G_END_DECLS
+
+#endif /* XFCE_DESKTOP_UTILS_H */
diff --git a/src/xfce-rr-config.c b/src/xfce-rr-config.c
new file mode 100644
index 0000000..92ded8d
--- /dev/null
+++ b/src/xfce-rr-config.c
@@ -0,0 +1,1978 @@
+/* xfce-rr-config.c
+ * -*- c-basic-offset: 4 -*-
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ * Copyright 2010 Giovanni Campagna
+ * 
+ * This file is part of the Mate Library.
+ * 
+ * The Mate Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Mate 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
+ * Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Mate Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ * 
+ * Author: Soren Sandmann <sandmann at redhat.com>
+ */
+
+
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <X11/Xlib.h>
+#include <gdk/gdkx.h>
+
+#include "xfce-rr-config.h"
+
+#include "edid.h"
+#include "xfce-rr-private.h"
+
+#define CONFIG_INTENDED_BASENAME "monitors.xml"
+#define CONFIG_BACKUP_BASENAME "monitors.xml.backup"
+
+/* In version 0 of the config file format, we had several <configuration>
+ * toplevel elements and no explicit version number.  So, the filed looked
+ * like
+ *
+ *   <configuration>
+ *     ...
+ *   </configuration>
+ *   <configuration>
+ *     ...
+ *   </configuration>
+ *
+ * Since version 1 of the config file, the file has a toplevel <monitors>
+ * element to group all the configurations.  That element has a "version"
+ * attribute which is an integer. So, the file looks like this:
+ *
+ *   <monitors version="1">
+ *     <configuration>
+ *       ...
+ *     </configuration>
+ *     <configuration>
+ *       ...
+ *     </configuration>
+ *   </monitors>
+ */
+
+/* A helper wrapper around the GMarkup parser stuff */
+static gboolean parse_file_gmarkup (const gchar *file,
+				    const GMarkupParser *parser,
+				    gpointer data,
+				    GError **err);
+
+typedef struct CrtcAssignment CrtcAssignment;
+
+static gboolean         crtc_assignment_apply (CrtcAssignment   *assign,
+					       guint32           timestamp,
+					       GError          **error);
+static CrtcAssignment  *crtc_assignment_new   (XfceRRScreen      *screen,
+					       XfceRROutputInfo **outputs,
+					       GError            **error);
+static void             crtc_assignment_free  (CrtcAssignment   *assign);
+
+enum {
+  PROP_0,
+  PROP_SCREEN,
+  PROP_LAST
+};
+
+G_DEFINE_TYPE (XfceRRConfig, xfce_rr_config, G_TYPE_OBJECT)
+
+typedef struct Parser Parser;
+
+/* Parser for monitor configurations */
+struct Parser
+{
+    int			config_file_version;
+    XfceRROutputInfo *	output;
+    XfceRRConfig *	configuration;
+    GPtrArray *		outputs;
+    GPtrArray *		configurations;
+    GQueue *		stack;
+};
+
+static int
+parse_int (const char *text)
+{
+    return strtol (text, NULL, 0);
+}
+
+static guint
+parse_uint (const char *text)
+{
+    return strtoul (text, NULL, 0);
+}
+
+static gboolean
+stack_is (Parser *parser,
+	  const char *s1,
+	  ...)
+{
+    GList *stack = NULL;
+    const char *s;
+    GList *l1, *l2;
+    va_list args;
+    
+    stack = g_list_prepend (stack, (gpointer)s1);
+    
+    va_start (args, s1);
+    
+    s = va_arg (args, const char *);
+    while (s)
+    {
+	stack = g_list_prepend (stack, (gpointer)s);
+	s = va_arg (args, const char *);
+    }
+
+    va_end (args);
+	
+    l1 = stack;
+    l2 = parser->stack->head;
+    
+    while (l1 && l2)
+    {
+	if (strcmp (l1->data, l2->data) != 0)
+	{
+	    g_list_free (stack);
+	    return FALSE;
+	}
+	
+	l1 = l1->next;
+	l2 = l2->next;
+    }
+    
+    g_list_free (stack);
+    
+    return (!l1 && !l2);
+}
+
+static void
+handle_start_element (GMarkupParseContext *context,
+		      const gchar         *name,
+		      const gchar        **attr_names,
+		      const gchar        **attr_values,
+		      gpointer             user_data,
+		      GError             **err)
+{
+    Parser *parser = user_data;
+
+    if (strcmp (name, "output") == 0)
+    {
+	int i;
+	g_assert (parser->output == NULL);
+
+	parser->output = g_object_new (XFCE_TYPE_RR_OUTPUT_INFO, NULL);
+	parser->output->priv->rotation = 0;
+	
+	for (i = 0; attr_names[i] != NULL; ++i)
+	{
+	    if (strcmp (attr_names[i], "name") == 0)
+	    {
+		parser->output->priv->name = g_strdup (attr_values[i]);
+		break;
+	    }
+	}
+
+	if (!parser->output->priv->name)
+	{
+	    /* This really shouldn't happen, but it's better to make
+	     * something up than to crash later.
+	     */
+	    g_warning ("Malformed monitor configuration file");
+	    
+	    parser->output->priv->name = g_strdup ("default");
+	}	
+	parser->output->priv->connected = FALSE;
+	parser->output->priv->on = FALSE;
+	parser->output->priv->primary = FALSE;
+    }
+    else if (strcmp (name, "configuration") == 0)
+    {
+	g_assert (parser->configuration == NULL);
+	
+	parser->configuration = g_object_new (XFCE_TYPE_RR_CONFIG, NULL);
+	parser->configuration->priv->clone = FALSE;
+	parser->configuration->priv->outputs = NULL;
+    }
+    else if (strcmp (name, "monitors") == 0)
+    {
+	int i;
+
+	for (i = 0; attr_names[i] != NULL; i++)
+	{
+	    if (strcmp (attr_names[i], "version") == 0)
+	    {
+		parser->config_file_version = parse_int (attr_values[i]);
+		break;
+	    }
+	}
+    }
+
+    g_queue_push_tail (parser->stack, g_strdup (name));
+}
+
+static void
+handle_end_element (GMarkupParseContext *context,
+		    const gchar         *name,
+		    gpointer             user_data,
+		    GError             **err)
+{
+    Parser *parser = user_data;
+    
+    if (strcmp (name, "output") == 0)
+    {
+	/* If no rotation properties were set, just use XFCE_RR_ROTATION_0 */
+	if (parser->output->priv->rotation == 0)
+	    parser->output->priv->rotation = XFCE_RR_ROTATION_0;
+	
+	g_ptr_array_add (parser->outputs, parser->output);
+
+	parser->output = NULL;
+    }
+    else if (strcmp (name, "configuration") == 0)
+    {
+	g_ptr_array_add (parser->outputs, NULL);
+	parser->configuration->priv->outputs =
+	    (XfceRROutputInfo **)g_ptr_array_free (parser->outputs, FALSE);
+	parser->outputs = g_ptr_array_new ();
+	g_ptr_array_add (parser->configurations, parser->configuration);
+	parser->configuration = NULL;
+    }
+    
+    g_free (g_queue_pop_tail (parser->stack));
+}
+
+#define TOPLEVEL_ELEMENT (parser->config_file_version > 0 ? "monitors" : NULL)
+
+static void
+handle_text (GMarkupParseContext *context,
+	     const gchar         *text,
+	     gsize                text_len,
+	     gpointer             user_data,
+	     GError             **err)
+{
+    Parser *parser = user_data;
+    
+    if (stack_is (parser, "vendor", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+    {
+	parser->output->priv->connected = TRUE;
+	
+	strncpy ((gchar*) parser->output->priv->vendor, text, 3);
+	parser->output->priv->vendor[3] = 0;
+    }
+    else if (stack_is (parser, "clone", "configuration", TOPLEVEL_ELEMENT, NULL))
+    {
+	if (strcmp (text, "yes") == 0)
+	    parser->configuration->priv->clone = TRUE;
+    }
+    else if (stack_is (parser, "product", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+    {
+	parser->output->priv->connected = TRUE;
+
+	parser->output->priv->product = parse_int (text);
+    }
+    else if (stack_is (parser, "serial", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+    {
+	parser->output->priv->connected = TRUE;
+
+	parser->output->priv->serial = parse_uint (text);
+    }
+    else if (stack_is (parser, "width", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+    {
+	parser->output->priv->on = TRUE;
+
+	parser->output->priv->width = parse_int (text);
+    }
+    else if (stack_is (parser, "x", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+    {
+	parser->output->priv->on = TRUE;
+
+	parser->output->priv->x = parse_int (text);
+    }
+    else if (stack_is (parser, "y", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+    {
+	parser->output->priv->on = TRUE;
+
+	parser->output->priv->y = parse_int (text);
+    }
+    else if (stack_is (parser, "height", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+    {
+	parser->output->priv->on = TRUE;
+
+	parser->output->priv->height = parse_int (text);
+    }
+    else if (stack_is (parser, "rate", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+    {
+	parser->output->priv->on = TRUE;
+
+	parser->output->priv->rate = parse_int (text);
+    }
+    else if (stack_is (parser, "rotation", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+    {
+	if (strcmp (text, "normal") == 0)
+	{
+	    parser->output->priv->rotation |= XFCE_RR_ROTATION_0;
+	}
+	else if (strcmp (text, "left") == 0)
+	{
+	    parser->output->priv->rotation |= XFCE_RR_ROTATION_90;
+	}
+	else if (strcmp (text, "upside_down") == 0)
+	{
+	    parser->output->priv->rotation |= XFCE_RR_ROTATION_180;
+	}
+	else if (strcmp (text, "right") == 0)
+	{
+	    parser->output->priv->rotation |= XFCE_RR_ROTATION_270;
+	}
+    }
+    else if (stack_is (parser, "reflect_x", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+    {
+	if (strcmp (text, "yes") == 0)
+	{
+	    parser->output->priv->rotation |= XFCE_RR_REFLECT_X;
+	}
+    }
+    else if (stack_is (parser, "reflect_y", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+    {
+	if (strcmp (text, "yes") == 0)
+	{
+	    parser->output->priv->rotation |= XFCE_RR_REFLECT_Y;
+	}
+    }
+    else if (stack_is (parser, "primary", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+    {
+	if (strcmp (text, "yes") == 0)
+	{
+	    parser->output->priv->primary = TRUE;
+	}
+    }
+    else
+    {
+	/* Ignore other properties so we can expand the format in the future */
+    }
+}
+
+static void
+parser_free (Parser *parser)
+{
+    int i;
+    GList *list;
+
+    g_assert (parser != NULL);
+
+    if (parser->output)
+	g_object_unref (parser->output);
+
+    if (parser->configuration)
+	g_object_unref (parser->configuration);
+
+    for (i = 0; i < parser->outputs->len; ++i)
+    {
+	XfceRROutputInfo *output = parser->outputs->pdata[i];
+
+	g_object_unref (output);
+    }
+
+    g_ptr_array_free (parser->outputs, TRUE);
+
+    for (i = 0; i < parser->configurations->len; ++i)
+    {
+	XfceRRConfig *config = parser->configurations->pdata[i];
+
+	g_object_unref (config);
+    }
+
+    g_ptr_array_free (parser->configurations, TRUE);
+
+    for (list = parser->stack->head; list; list = list->next)
+	g_free (list->data);
+    g_queue_free (parser->stack);
+    
+    g_free (parser);
+}
+
+static XfceRRConfig **
+configurations_read_from_file (const gchar *filename, GError **error)
+{
+    Parser *parser = g_new0 (Parser, 1);
+    XfceRRConfig **result;
+    GMarkupParser callbacks = {
+	handle_start_element,
+	handle_end_element,
+	handle_text,
+	NULL, /* passthrough */
+	NULL, /* error */
+    };
+
+    parser->config_file_version = 0;
+    parser->configurations = g_ptr_array_new ();
+    parser->outputs = g_ptr_array_new ();
+    parser->stack = g_queue_new ();
+    
+    if (!parse_file_gmarkup (filename, &callbacks, parser, error))
+    {
+	result = NULL;
+	
+	g_assert (parser->outputs);
+	goto out;
+    }
+
+    g_assert (parser->outputs);
+    
+    g_ptr_array_add (parser->configurations, NULL);
+    result = (XfceRRConfig **)g_ptr_array_free (parser->configurations, FALSE);
+    parser->configurations = g_ptr_array_new ();
+    
+    g_assert (parser->outputs);
+out:
+    parser_free (parser);
+
+    return result;
+}
+
+static void
+xfce_rr_config_init (XfceRRConfig *self)
+{
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, XFCE_TYPE_RR_CONFIG, XfceRRConfigPrivate);
+
+    self->priv->clone = FALSE;
+    self->priv->screen = NULL;
+    self->priv->outputs = NULL;
+}
+
+static void
+xfce_rr_config_set_property (GObject *gobject, guint property_id, const GValue *value, GParamSpec *property)
+{
+    XfceRRConfig *self = XFCE_RR_CONFIG (gobject);
+
+    switch (property_id) {
+	case PROP_SCREEN:
+	    self->priv->screen = g_value_dup_object (value);
+	    return;
+	default:
+	    G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, property);
+    }
+}
+
+static void
+xfce_rr_config_finalize (GObject *gobject)
+{
+    XfceRRConfig *self = XFCE_RR_CONFIG (gobject);
+
+    if (self->priv->screen)
+	g_object_unref (self->priv->screen);
+
+    if (self->priv->outputs) {
+	int i;
+
+        for (i = 0; self->priv->outputs[i] != NULL; i++) {
+	    XfceRROutputInfo *output = self->priv->outputs[i];
+	    g_object_unref (output);
+	}
+	g_free (self->priv->outputs);
+    }
+
+    G_OBJECT_CLASS (xfce_rr_config_parent_class)->finalize (gobject);
+}
+
+gboolean
+xfce_rr_config_load_current (XfceRRConfig *config, GError **error)
+{
+    GPtrArray *a;
+    XfceRROutput **rr_outputs;
+    int i;
+    int clone_width = -1;
+    int clone_height = -1;
+    int last_x;
+
+    g_return_val_if_fail (XFCE_IS_RR_CONFIG (config), FALSE);
+
+    a = g_ptr_array_new ();
+    rr_outputs = xfce_rr_screen_list_outputs (config->priv->screen);
+
+    config->priv->clone = FALSE;
+    
+    for (i = 0; rr_outputs[i] != NULL; ++i)
+    {
+	XfceRROutput *rr_output = rr_outputs[i];
+	XfceRROutputInfo *output = g_object_new (XFCE_TYPE_RR_OUTPUT_INFO, NULL);
+	XfceRRMode *mode = NULL;
+	const guint8 *edid_data = xfce_rr_output_get_edid_data (rr_output);
+	XfceRRCrtc *crtc;
+
+	output->priv->name = g_strdup (xfce_rr_output_get_name (rr_output));
+	output->priv->connected = xfce_rr_output_is_connected (rr_output);
+
+	if (!output->priv->connected)
+	{
+	    output->priv->x = -1;
+	    output->priv->y = -1;
+	    output->priv->width = -1;
+	    output->priv->height = -1;
+	    output->priv->rate = -1;
+	    output->priv->rotation = XFCE_RR_ROTATION_0;
+	}
+	else
+	{
+	    MonitorInfo *info = NULL;
+
+	    if (edid_data)
+		info = decode_edid (edid_data);
+
+	    if (info)
+	    {
+		memcpy (output->priv->vendor, info->manufacturer_code,
+			sizeof (output->priv->vendor));
+		
+		output->priv->product = info->product_code;
+		output->priv->serial = info->serial_number;
+		output->priv->aspect = info->aspect_ratio;
+	    }
+	    else
+	    {
+		strcpy (output->priv->vendor, "???");
+		output->priv->product = 0;
+		output->priv->serial = 0;
+	    }
+
+	    if (xfce_rr_output_is_laptop (rr_output))
+		output->priv->display_name = g_strdup (_("Laptop"));
+	    else
+		output->priv->display_name = make_display_name (info);
+		
+	    g_free (info);
+		
+	    crtc = xfce_rr_output_get_crtc (rr_output);
+	    mode = crtc? xfce_rr_crtc_get_current_mode (crtc) : NULL;
+	    
+	    if (crtc && mode)
+	    {
+		output->priv->on = TRUE;
+		
+		xfce_rr_crtc_get_position (crtc, &output->priv->x, &output->priv->y);
+		output->priv->width = xfce_rr_mode_get_width (mode);
+		output->priv->height = xfce_rr_mode_get_height (mode);
+		output->priv->rate = xfce_rr_mode_get_freq (mode);
+		output->priv->rotation = xfce_rr_crtc_get_current_rotation (crtc);
+
+		if (output->priv->x == 0 && output->priv->y == 0) {
+			if (clone_width == -1) {
+				clone_width = output->priv->width;
+				clone_height = output->priv->height;
+			} else if (clone_width == output->priv->width &&
+				   clone_height == output->priv->height) {
+				config->priv->clone = TRUE;
+			}
+		}
+	    }
+	    else
+	    {
+		output->priv->on = FALSE;
+		config->priv->clone = FALSE;
+	    }
+
+	    /* Get preferred size for the monitor */
+	    mode = xfce_rr_output_get_preferred_mode (rr_output);
+	    
+	    if (!mode)
+	    {
+		XfceRRMode **modes = xfce_rr_output_list_modes (rr_output);
+		
+		/* FIXME: we should pick the "best" mode here, where best is
+		 * sorted wrt
+		 *
+		 * - closest aspect ratio
+		 * - mode area
+		 * - refresh rate
+		 * - We may want to extend randrwrap so that get_preferred
+		 *   returns that - although that could also depend on
+		 *   the crtc.
+		 */
+		if (modes[0])
+		    mode = modes[0];
+	    }
+	    
+	    if (mode)
+	    {
+		output->priv->pref_width = xfce_rr_mode_get_width (mode);
+		output->priv->pref_height = xfce_rr_mode_get_height (mode);
+	    }
+	    else
+	    {
+		/* Pick some random numbers. This should basically never happen */
+		output->priv->pref_width = 1024;
+		output->priv->pref_height = 768;
+	    }
+	}
+
+        output->priv->primary = xfce_rr_output_get_is_primary (rr_output);
+ 
+	g_ptr_array_add (a, output);
+    }
+
+    g_ptr_array_add (a, NULL);
+    
+    config->priv->outputs = (XfceRROutputInfo **)g_ptr_array_free (a, FALSE);
+
+    /* Walk the outputs computing the right-most edge of all
+     * lit-up displays
+     */
+    last_x = 0;
+    for (i = 0; config->priv->outputs[i] != NULL; ++i)
+    {
+	XfceRROutputInfo *output = config->priv->outputs[i];
+
+	if (output->priv->on)
+	{
+	    last_x = MAX (last_x, output->priv->x + output->priv->width);
+	}
+    }
+
+    /* Now position all off displays to the right of the
+     * on displays
+     */
+    for (i = 0; config->priv->outputs[i] != NULL; ++i)
+    {
+	XfceRROutputInfo *output = config->priv->outputs[i];
+
+	if (output->priv->connected && !output->priv->on)
+	{
+	    output->priv->x = last_x;
+	    last_x = output->priv->x + output->priv->width;
+	}
+    }
+    
+    g_assert (xfce_rr_config_match (config, config));
+
+    return TRUE;
+}
+
+gboolean
+xfce_rr_config_load_filename (XfceRRConfig *result, const char *filename, GError **error)
+{
+    XfceRRConfig *current;
+    XfceRRConfig **configs;
+    gboolean found = FALSE;
+
+    g_return_val_if_fail (XFCE_IS_RR_CONFIG (result), FALSE);
+    g_return_val_if_fail (filename != NULL, FALSE);
+    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+    if (filename == NULL)
+      filename = xfce_rr_config_get_intended_filename ();
+
+    current = xfce_rr_config_new_current (result->priv->screen, error);
+
+    configs = configurations_read_from_file (filename, error);
+
+    if (configs)
+    {
+	int i;
+
+	for (i = 0; configs[i] != NULL; ++i)
+	{
+	    if (xfce_rr_config_match (configs[i], current))
+	    {
+		int j;
+		GPtrArray *array;
+		result->priv->clone = configs[i]->priv->clone;
+
+		array = g_ptr_array_new ();
+		for (j = 0; configs[i]->priv->outputs[j] != NULL; j++) {
+		    g_object_ref (configs[i]->priv->outputs[j]);
+		    g_ptr_array_add (array, configs[i]->priv->outputs[j]);
+		}
+		g_ptr_array_add (array, NULL);
+		result->priv->outputs = (XfceRROutputInfo **) g_ptr_array_free (array, FALSE);
+
+		found = TRUE;
+		break;
+	    }
+	    g_object_unref (configs[i]);
+	}
+	g_free (configs);
+
+	if (!found)
+	    g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_NO_MATCHING_CONFIG,
+			 _("none of the saved display configurations matched the active configuration"));
+    }
+
+    g_object_unref (current);
+    return found;
+}
+
+static void
+xfce_rr_config_class_init (XfceRRConfigClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    g_type_class_add_private (klass, sizeof (XfceRROutputInfoPrivate));
+
+    gobject_class->set_property = xfce_rr_config_set_property;
+    gobject_class->finalize = xfce_rr_config_finalize;
+
+    g_object_class_install_property (gobject_class, PROP_SCREEN,
+				     g_param_spec_object ("screen", "Screen", "The XfceRRScreen this config applies to", XFCE_TYPE_RR_SCREEN,
+							  G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
+}
+
+XfceRRConfig *
+xfce_rr_config_new_current (XfceRRScreen *screen, GError **error)
+{
+    XfceRRConfig *self = g_object_new (XFCE_TYPE_RR_CONFIG, "screen", screen, NULL);
+
+    if (xfce_rr_config_load_current (self, error))
+      return self;
+    else
+      {
+	g_object_unref (self);
+	return NULL;
+      }
+}
+
+XfceRRConfig *
+xfce_rr_config_new_stored (XfceRRScreen *screen, GError **error)
+{
+    XfceRRConfig *self = g_object_new (XFCE_TYPE_RR_CONFIG, "screen", screen, NULL);
+    char *filename;
+    gboolean success;
+
+    filename = xfce_rr_config_get_intended_filename ();
+
+    success = xfce_rr_config_load_filename (self, filename, error);
+
+    g_free (filename);
+
+    if (success)
+      return self;
+    else
+      {
+	g_object_unref (self);
+	return NULL;
+      }
+}
+
+static gboolean
+parse_file_gmarkup (const gchar          *filename,
+		    const GMarkupParser  *parser,
+		    gpointer             data,
+		    GError              **err)
+{
+    GMarkupParseContext *context = NULL;
+    gchar *contents = NULL;
+    gboolean result = TRUE;
+    gsize len;
+
+    if (!g_file_get_contents (filename, &contents, &len, err))
+    {
+	result = FALSE;
+	goto out;
+    }
+    
+    context = g_markup_parse_context_new (parser, 0, data, NULL);
+
+    if (!g_markup_parse_context_parse (context, contents, len, err))
+    {
+	result = FALSE;
+	goto out;
+    }
+
+    if (!g_markup_parse_context_end_parse (context, err))
+    {
+	result = FALSE;
+	goto out;
+    }
+
+out:
+    if (contents)
+	g_free (contents);
+
+    if (context)
+	g_markup_parse_context_free (context);
+
+    return result;
+}
+
+static gboolean
+output_match (XfceRROutputInfo *output1, XfceRROutputInfo *output2)
+{
+    g_assert (XFCE_IS_RR_OUTPUT_INFO (output1));
+    g_assert (XFCE_IS_RR_OUTPUT_INFO (output2));
+
+    if (strcmp (output1->priv->name, output2->priv->name) != 0)
+	return FALSE;
+
+    if (strcmp (output1->priv->vendor, output2->priv->vendor) != 0)
+	return FALSE;
+
+    if (output1->priv->product != output2->priv->product)
+	return FALSE;
+
+    if (output1->priv->serial != output2->priv->serial)
+	return FALSE;
+
+    if (output1->priv->connected != output2->priv->connected)
+	return FALSE;
+    
+    return TRUE;
+}
+
+static gboolean
+output_equal (XfceRROutputInfo *output1, XfceRROutputInfo *output2)
+{
+    g_assert (XFCE_IS_RR_OUTPUT_INFO (output1));
+    g_assert (XFCE_IS_RR_OUTPUT_INFO (output2));
+
+    if (!output_match (output1, output2))
+	return FALSE;
+
+    if (output1->priv->on != output2->priv->on)
+	return FALSE;
+
+    if (output1->priv->on)
+    {
+	if (output1->priv->width != output2->priv->width)
+	    return FALSE;
+	
+	if (output1->priv->height != output2->priv->height)
+	    return FALSE;
+	
+	if (output1->priv->rate != output2->priv->rate)
+	    return FALSE;
+	
+	if (output1->priv->x != output2->priv->x)
+	    return FALSE;
+	
+	if (output1->priv->y != output2->priv->y)
+	    return FALSE;
+	
+	if (output1->priv->rotation != output2->priv->rotation)
+	    return FALSE;
+    }
+
+    return TRUE;
+}
+
+static XfceRROutputInfo *
+find_output (XfceRRConfig *config, const char *name)
+{
+    int i;
+
+    for (i = 0; config->priv->outputs[i] != NULL; ++i)
+    {
+	XfceRROutputInfo *output = config->priv->outputs[i];
+	
+	if (strcmp (name, output->priv->name) == 0)
+	    return output;
+    }
+
+    return NULL;
+}
+
+/* Match means "these configurations apply to the same hardware
+ * setups"
+ */
+gboolean
+xfce_rr_config_match (XfceRRConfig *c1, XfceRRConfig *c2)
+{
+    int i;
+    g_return_val_if_fail (XFCE_IS_RR_CONFIG (c1), FALSE);
+    g_return_val_if_fail (XFCE_IS_RR_CONFIG (c2), FALSE);
+
+    for (i = 0; c1->priv->outputs[i] != NULL; ++i)
+    {
+	XfceRROutputInfo *output1 = c1->priv->outputs[i];
+	XfceRROutputInfo *output2;
+
+	output2 = find_output (c2, output1->priv->name);
+	if (!output2 || !output_match (output1, output2))
+	    return FALSE;
+    }
+    
+    return TRUE;
+}
+
+/* Equal means "the configurations will result in the same
+ * modes being set on the outputs"
+ */
+gboolean
+xfce_rr_config_equal (XfceRRConfig  *c1,
+		       XfceRRConfig  *c2)
+{
+    int i;
+    g_return_val_if_fail (XFCE_IS_RR_CONFIG (c1), FALSE);
+    g_return_val_if_fail (XFCE_IS_RR_CONFIG (c2), FALSE);
+
+    for (i = 0; c1->priv->outputs[i] != NULL; ++i)
+    {
+	XfceRROutputInfo *output1 = c1->priv->outputs[i];
+	XfceRROutputInfo *output2;
+
+	output2 = find_output (c2, output1->priv->name);
+	if (!output2 || !output_equal (output1, output2))
+	    return FALSE;
+    }
+    
+    return TRUE;
+}
+
+static XfceRROutputInfo **
+make_outputs (XfceRRConfig *config)
+{
+    GPtrArray *outputs;
+    XfceRROutputInfo *first_on;
+    int i;
+
+    outputs = g_ptr_array_new ();
+
+    first_on = NULL;
+    
+    for (i = 0; config->priv->outputs[i] != NULL; ++i)
+    {
+	XfceRROutputInfo *old = config->priv->outputs[i];
+	XfceRROutputInfo *new = g_object_new (XFCE_TYPE_RR_OUTPUT_INFO, NULL);
+	*(new->priv) = *(old->priv);
+	if (old->priv->name)
+	    new->priv->name = g_strdup (old->priv->name);
+	if (old->priv->display_name)
+	    new->priv->display_name = g_strdup (old->priv->display_name);
+
+	if (old->priv->on && !first_on)
+	    first_on = old;
+	
+	if (config->priv->clone && new->priv->on)
+	{
+	    g_assert (first_on);
+
+	    new->priv->width = first_on->priv->width;
+	    new->priv->height = first_on->priv->height;
+	    new->priv->rotation = first_on->priv->rotation;
+	    new->priv->x = 0;
+	    new->priv->y = 0;
+	}
+
+	g_ptr_array_add (outputs, new);
+    }
+
+    g_ptr_array_add (outputs, NULL);
+
+    return (XfceRROutputInfo **)g_ptr_array_free (outputs, FALSE);
+}
+
+gboolean
+xfce_rr_config_applicable (XfceRRConfig  *configuration,
+			    XfceRRScreen  *screen,
+			    GError        **error)
+{
+    XfceRROutputInfo **outputs;
+    CrtcAssignment *assign;
+    gboolean result;
+    int i;
+
+    g_return_val_if_fail (XFCE_IS_RR_CONFIG (configuration), FALSE);
+    g_return_val_if_fail (XFCE_IS_RR_SCREEN (screen), FALSE);
+    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+    outputs = make_outputs (configuration);
+    assign = crtc_assignment_new (screen, outputs, error);
+
+    if (assign)
+    {
+	result = TRUE;
+	crtc_assignment_free (assign);
+    }
+    else
+    {
+	result = FALSE;
+    }
+
+    for (i = 0; outputs[i] != NULL; i++) {
+	 g_object_unref (outputs[i]);
+    }
+
+    return result;
+}
+
+/* Database management */
+
+static void
+ensure_config_directory (void)
+{
+    g_mkdir_with_parents (g_get_user_config_dir (), 0700);
+}
+
+char *
+xfce_rr_config_get_backup_filename (void)
+{
+    ensure_config_directory ();
+    return g_build_filename (g_get_user_config_dir (), CONFIG_BACKUP_BASENAME, NULL);
+}
+
+char *
+xfce_rr_config_get_intended_filename (void)
+{
+    ensure_config_directory ();
+    return g_build_filename (g_get_user_config_dir (), CONFIG_INTENDED_BASENAME, NULL);
+}
+
+static const char *
+get_rotation_name (XfceRRRotation r)
+{
+    if (r & XFCE_RR_ROTATION_0)
+	return "normal";
+    if (r & XFCE_RR_ROTATION_90)
+	return "left";
+    if (r & XFCE_RR_ROTATION_180)
+	return "upside_down";
+    if (r & XFCE_RR_ROTATION_270)
+	return "right";
+
+    return "normal";
+}
+
+static const char *
+yes_no (int x)
+{
+    return x? "yes" : "no";
+}
+
+static const char *
+get_reflect_x (XfceRRRotation r)
+{
+    return yes_no (r & XFCE_RR_REFLECT_X);
+}
+
+static const char *
+get_reflect_y (XfceRRRotation r)
+{
+    return yes_no (r & XFCE_RR_REFLECT_Y);
+}
+
+static void
+emit_configuration (XfceRRConfig *config,
+		    GString *string)
+{
+    int j;
+
+    g_string_append_printf (string, "  <configuration>\n");
+
+    g_string_append_printf (string, "      <clone>%s</clone>\n", yes_no (config->priv->clone));
+    
+    for (j = 0; config->priv->outputs[j] != NULL; ++j)
+    {
+	XfceRROutputInfo *output = config->priv->outputs[j];
+	
+	g_string_append_printf (
+	    string, "      <output name=\"%s\">\n", output->priv->name);
+	
+	if (output->priv->connected && *output->priv->vendor != '\0')
+	{
+	    g_string_append_printf (
+		string, "          <vendor>%s</vendor>\n", output->priv->vendor);
+	    g_string_append_printf (
+		string, "          <product>0x%04x</product>\n", output->priv->product);
+	    g_string_append_printf (
+		string, "          <serial>0x%08x</serial>\n", output->priv->serial);
+	}
+	
+	/* An unconnected output which is on does not make sense */
+	if (output->priv->connected && output->priv->on)
+	{
+	    g_string_append_printf (
+		string, "          <width>%d</width>\n", output->priv->width);
+	    g_string_append_printf (
+		string, "          <height>%d</height>\n", output->priv->height);
+	    g_string_append_printf (
+		string, "          <rate>%d</rate>\n", output->priv->rate);
+	    g_string_append_printf (
+		string, "          <x>%d</x>\n", output->priv->x);
+	    g_string_append_printf (
+		string, "          <y>%d</y>\n", output->priv->y);
+	    g_string_append_printf (
+		string, "          <rotation>%s</rotation>\n", get_rotation_name (output->priv->rotation));
+	    g_string_append_printf (
+		string, "          <reflect_x>%s</reflect_x>\n", get_reflect_x (output->priv->rotation));
+	    g_string_append_printf (
+		string, "          <reflect_y>%s</reflect_y>\n", get_reflect_y (output->priv->rotation));
+            g_string_append_printf (
+                string, "          <primary>%s</primary>\n", yes_no (output->priv->primary));
+	}
+	
+	g_string_append_printf (string, "      </output>\n");
+    }
+    
+    g_string_append_printf (string, "  </configuration>\n");
+}
+
+void
+xfce_rr_config_sanitize (XfceRRConfig *config)
+{
+    int i;
+    int x_offset, y_offset;
+    gboolean found;
+
+    /* Offset everything by the top/left-most coordinate to
+     * make sure the configuration starts at (0, 0)
+     */
+    x_offset = y_offset = G_MAXINT;
+    for (i = 0; config->priv->outputs[i]; ++i)
+    {
+	XfceRROutputInfo *output = config->priv->outputs[i];
+
+	if (output->priv->on)
+	{
+	    x_offset = MIN (x_offset, output->priv->x);
+	    y_offset = MIN (y_offset, output->priv->y);
+	}
+    }
+
+    for (i = 0; config->priv->outputs[i]; ++i)
+    {
+	XfceRROutputInfo *output = config->priv->outputs[i];
+	
+	if (output->priv->on)
+	{
+	    output->priv->x -= x_offset;
+	    output->priv->y -= y_offset;
+	}
+    }
+
+    /* Only one primary, please */
+    found = FALSE;
+    for (i = 0; config->priv->outputs[i]; ++i)
+    {
+        if (config->priv->outputs[i]->priv->primary)
+        {
+            if (found)
+            {
+                config->priv->outputs[i]->priv->primary = FALSE;
+            }
+            else
+            {
+                found = TRUE;
+            }
+        }
+    }
+}
+
+gboolean
+xfce_rr_config_ensure_primary (XfceRRConfig *configuration)
+{
+        int              i;
+        XfceRROutputInfo  *laptop;
+        XfceRROutputInfo  *top_left;
+        gboolean        found;
+        XfceRRConfigPrivate *priv;
+
+        g_return_val_if_fail (XFCE_IS_RR_CONFIG (configuration), FALSE);
+
+        laptop = NULL;
+        top_left = NULL;
+        found = FALSE;
+        priv = configuration->priv;
+
+        for (i = 0; priv->outputs[i] != NULL; ++i) {
+                XfceRROutputInfo *info = priv->outputs[i];
+
+                if (! info->priv->on) {
+                        info->priv->primary = FALSE;
+                        continue;
+		}
+
+                /* ensure only one */
+                if (info->priv->primary) {
+                        if (found) {
+                                info->priv->primary = FALSE;
+                        } else {
+                                found = TRUE;
+                        }
+                }
+
+                if (top_left == NULL
+                    || (info->priv->x < top_left->priv->x
+                        && info->priv->y < top_left->priv->y)) {
+                        top_left = info;
+                }
+                if (laptop == NULL
+                    && _xfce_rr_output_name_is_laptop (info->priv->name)) {
+                        laptop = info;
+                }
+        }
+
+        if (!found) {
+                if (laptop != NULL) {
+                        laptop->priv->primary = TRUE;
+                } else if (top_left != NULL) {
+		        /* Note: top_left can be NULL if all outputs are off */
+                        top_left->priv->primary = TRUE;
+                }
+        }
+
+        return !found;
+}
+
+gboolean
+xfce_rr_config_save (XfceRRConfig *configuration, GError **error)
+{
+    XfceRRConfig **configurations;
+    GString *output;
+    int i;
+    gchar *intended_filename;
+    gchar *backup_filename;
+    gboolean result;
+
+    g_return_val_if_fail (XFCE_IS_RR_CONFIG (configuration), FALSE);
+    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+    output = g_string_new ("");
+
+    backup_filename = xfce_rr_config_get_backup_filename ();
+    intended_filename = xfce_rr_config_get_intended_filename ();
+
+    configurations = configurations_read_from_file (intended_filename, NULL); /* NULL-GError */
+    
+    g_string_append_printf (output, "<monitors version=\"1\">\n");
+
+    if (configurations)
+    {
+	for (i = 0; configurations[i] != NULL; ++i)
+	{
+	    if (!xfce_rr_config_match (configurations[i], configuration))
+		emit_configuration (configurations[i], output);
+	    g_object_unref (configurations[i]);
+	}
+
+	g_free (configurations);
+    }
+
+    emit_configuration (configuration, output);
+
+    g_string_append_printf (output, "</monitors>\n");
+
+    /* backup the file first */
+    rename (intended_filename, backup_filename); /* no error checking because the intended file may not even exist */
+
+    result = g_file_set_contents (intended_filename, output->str, -1, error);
+
+    if (!result)
+	rename (backup_filename, intended_filename); /* no error checking because the backup may not even exist */
+
+    g_free (backup_filename);
+    g_free (intended_filename);
+    g_string_free (output, TRUE);
+
+    return result;
+}
+
+gboolean
+xfce_rr_config_apply_with_time (XfceRRConfig *config,
+				 XfceRRScreen *screen,
+				 guint32        timestamp,
+				 GError       **error)
+{
+    CrtcAssignment *assignment;
+    XfceRROutputInfo **outputs;
+    gboolean result = FALSE;
+    int i;
+    GdkDisplay *display;
+
+    g_return_val_if_fail (XFCE_IS_RR_CONFIG (config), FALSE);
+    g_return_val_if_fail (XFCE_IS_RR_SCREEN (screen), FALSE);
+
+    outputs = make_outputs (config);
+
+    assignment = crtc_assignment_new (screen, outputs, error);
+
+    for (i = 0; outputs[i] != NULL; i++)
+	g_object_unref (outputs[i]);
+    g_free (outputs);
+    
+    if (assignment)
+    {
+	if (crtc_assignment_apply (assignment, timestamp, error))
+	    result = TRUE;
+
+	crtc_assignment_free (assignment);
+
+	display = gdk_display_get_default ();
+	gdk_display_flush (display);
+    }
+
+    return result;
+}
+
+/* xfce_rr_config_apply_from_filename_with_time:
+ * @screen: A #XfceRRScreen
+ * @filename: Path of the file to look in for stored RANDR configurations.
+ * @timestamp: X server timestamp from the event that causes the screen configuration to change (a user's button press, for example)
+ * @error: Location to store error, or %NULL
+ *
+ * First, this function refreshes the @screen to match the current RANDR
+ * configuration from the X server.  Then, it tries to load the file in
+ * @filename and looks for suitable matching RANDR configurations in the file;
+ * if one is found, that configuration will be applied to the current set of
+ * RANDR outputs.
+ *
+ * Typically, @filename is the result of xfce_rr_config_get_intended_filename() or
+ * xfce_rr_config_get_backup_filename().
+ *
+ * Returns: TRUE if the RANDR configuration was loaded and applied from
+ * $(XDG_CONFIG_HOME)/monitors.xml, or FALSE otherwise:
+ *
+ * If the current RANDR configuration could not be refreshed, the @error will
+ * have a domain of #XFCE_RR_ERROR and a corresponding error code.
+ *
+ * If the file in question is loaded successfully but the configuration cannot
+ * be applied, the @error will have a domain of #XFCE_RR_ERROR.  Note that an
+ * error code of #XFCE_RR_ERROR_NO_MATCHING_CONFIG is not a real error; it
+ * simply means that there were no stored configurations that match the current
+ * set of RANDR outputs.
+ *
+ * If the file in question cannot be loaded, the @error will have a domain of
+ * #G_FILE_ERROR.  Note that an error code of G_FILE_ERROR_NOENT is not really
+ * an error, either; it means that there was no stored configuration file and so
+ * nothing is changed.
+ */
+gboolean
+xfce_rr_config_apply_from_filename_with_time (XfceRRScreen *screen, const char *filename, guint32 timestamp, GError **error)
+{
+    XfceRRConfig *stored;
+    GError *my_error;
+
+    g_return_val_if_fail (XFCE_IS_RR_SCREEN (screen), FALSE);
+    g_return_val_if_fail (filename != NULL, FALSE);
+    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+    my_error = NULL;
+    if (!xfce_rr_screen_refresh (screen, &my_error)) {
+	    if (my_error) {
+		    g_propagate_error (error, my_error);
+		    return FALSE; /* This is a genuine error */
+	    }
+
+	    /* This means the screen didn't change, so just proceed */
+    }
+
+    stored = g_object_new (XFCE_TYPE_RR_CONFIG, "screen", screen, NULL);
+
+    if (xfce_rr_config_load_filename (stored, filename, error))
+    {
+	gboolean result;
+
+	xfce_rr_config_ensure_primary (stored);
+	result = xfce_rr_config_apply_with_time (stored, screen, timestamp, error);
+
+	g_object_unref (stored);
+	
+	return result;
+    }
+    else
+    {
+        g_object_unref (stored);
+	return FALSE;
+    }
+}
+
+/**
+ * xfce_rr_config_get_outputs:
+ *
+ * Returns: (array zero-terminated=1) (element-type MateDesktop.RROutputInfo) (transfer none): the output configuration for this #XfceRRConfig
+ */
+XfceRROutputInfo **
+xfce_rr_config_get_outputs (XfceRRConfig *self)
+{
+    g_return_val_if_fail (XFCE_IS_RR_CONFIG (self), NULL);
+
+    return self->priv->outputs;
+}
+
+/**
+ * xfce_rr_config_get_clone:
+ *
+ * Returns: whether at least two outputs are at (0, 0) offset and they
+ * have the same width/height.  Those outputs are of course connected and on
+ * (i.e. they have a CRTC assigned).
+ */
+gboolean
+xfce_rr_config_get_clone (XfceRRConfig *self)
+{
+    g_return_val_if_fail (XFCE_IS_RR_CONFIG (self), FALSE);
+
+    return self->priv->clone;
+}
+
+void
+xfce_rr_config_set_clone (XfceRRConfig *self, gboolean clone)
+{
+    g_return_if_fail (XFCE_IS_RR_CONFIG (self));
+
+    self->priv->clone = clone;
+}
+
+
+/*
+ * CRTC assignment
+ */
+typedef struct CrtcInfo CrtcInfo;
+
+struct CrtcInfo
+{
+    XfceRRMode    *mode;
+    int        x;
+    int        y;
+    XfceRRRotation rotation;
+    GPtrArray *outputs;
+};
+
+struct CrtcAssignment
+{
+    XfceRRScreen *screen;
+    GHashTable *info;
+    XfceRROutput *primary;
+};
+
+static gboolean
+can_clone (CrtcInfo *info,
+	   XfceRROutput *output)
+{
+    int i;
+
+    for (i = 0; i < info->outputs->len; ++i)
+    {
+	XfceRROutput *clone = info->outputs->pdata[i];
+
+	if (!xfce_rr_output_can_clone (clone, output))
+	    return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean
+crtc_assignment_assign (CrtcAssignment   *assign,
+			XfceRRCrtc      *crtc,
+			XfceRRMode      *mode,
+			int               x,
+			int               y,
+			XfceRRRotation   rotation,
+                        gboolean          primary,
+			XfceRROutput    *output,
+			GError          **error)
+{
+    CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+    guint32 crtc_id;
+    const char *output_name;
+
+    crtc_id = xfce_rr_crtc_get_id (crtc);
+    output_name = xfce_rr_output_get_name (output);
+
+    if (!xfce_rr_crtc_can_drive_output (crtc, output))
+    {
+	g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_CRTC_ASSIGNMENT,
+		     _("CRTC %d cannot drive output %s"), crtc_id, output_name);
+	return FALSE;
+    }
+
+    if (!xfce_rr_output_supports_mode (output, mode))
+    {
+	g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_CRTC_ASSIGNMENT,
+		     _("output %s does not support mode %dx%d@%dHz"),
+		     output_name,
+		     xfce_rr_mode_get_width (mode),
+		     xfce_rr_mode_get_height (mode),
+		     xfce_rr_mode_get_freq (mode));
+	return FALSE;
+    }
+
+    if (!xfce_rr_crtc_supports_rotation (crtc, rotation))
+    {
+	g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_CRTC_ASSIGNMENT,
+		     _("CRTC %d does not support rotation=%s"),
+		     crtc_id,
+		     get_rotation_name (rotation));
+	return FALSE;
+    }
+
+    if (info)
+    {
+	if (!(info->mode == mode	&&
+	      info->x == x		&&
+	      info->y == y		&&
+	      info->rotation == rotation))
+	{
+	    g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_CRTC_ASSIGNMENT,
+			 _("output %s does not have the same parameters as another cloned output:\n"
+			   "existing mode = %d, new mode = %d\n"
+			   "existing coordinates = (%d, %d), new coordinates = (%d, %d)\n"
+			   "existing rotation = %s, new rotation = %s"),
+			 output_name,
+			 xfce_rr_mode_get_id (info->mode), xfce_rr_mode_get_id (mode),
+			 info->x, info->y,
+			 x, y,
+			 get_rotation_name (info->rotation), get_rotation_name (rotation));
+	    return FALSE;
+	}
+
+	if (!can_clone (info, output))
+	{
+	    g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_CRTC_ASSIGNMENT,
+			 _("cannot clone to output %s"),
+			 output_name);
+	    return FALSE;
+	}
+
+	g_ptr_array_add (info->outputs, output);
+
+	if (primary && !assign->primary)
+	{
+	    assign->primary = output;
+	}
+
+	return TRUE;
+    }
+    else
+    {	
+	CrtcInfo *info = g_new0 (CrtcInfo, 1);
+	
+	info->mode = mode;
+	info->x = x;
+	info->y = y;
+	info->rotation = rotation;
+	info->outputs = g_ptr_array_new ();
+	
+	g_ptr_array_add (info->outputs, output);
+	
+	g_hash_table_insert (assign->info, crtc, info);
+	    
+        if (primary && !assign->primary)
+        {
+            assign->primary = output;
+        }
+
+	return TRUE;
+    }
+}
+
+static void
+crtc_assignment_unassign (CrtcAssignment *assign,
+			  XfceRRCrtc         *crtc,
+			  XfceRROutput       *output)
+{
+    CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+
+    if (info)
+    {
+	g_ptr_array_remove (info->outputs, output);
+
+        if (assign->primary == output)
+        {
+            assign->primary = NULL;
+        }
+
+	if (info->outputs->len == 0)
+	    g_hash_table_remove (assign->info, crtc);
+    }
+}
+
+static void
+crtc_assignment_free (CrtcAssignment *assign)
+{
+    g_hash_table_destroy (assign->info);
+
+    g_free (assign);
+}
+
+typedef struct {
+    guint32 timestamp;
+    gboolean has_error;
+    GError **error;
+} ConfigureCrtcState;
+
+static void
+configure_crtc (gpointer key,
+		gpointer value,
+		gpointer data)
+{
+    XfceRRCrtc *crtc = key;
+    CrtcInfo *info = value;
+    ConfigureCrtcState *state = data;
+
+    if (state->has_error)
+	return;
+
+    if (!xfce_rr_crtc_set_config_with_time (crtc,
+					     state->timestamp,
+					     info->x, info->y,
+					     info->mode,
+					     info->rotation,
+					     (XfceRROutput **)info->outputs->pdata,
+					     info->outputs->len,
+					     state->error))
+	state->has_error = TRUE;
+}
+
+static gboolean
+mode_is_rotated (CrtcInfo *info)
+{
+    if ((info->rotation & XFCE_RR_ROTATION_270)		||
+	(info->rotation & XFCE_RR_ROTATION_90))
+    {
+	return TRUE;
+    }
+    return FALSE;
+}
+
+static gboolean
+crtc_is_rotated (XfceRRCrtc *crtc)
+{
+    XfceRRRotation r = xfce_rr_crtc_get_current_rotation (crtc);
+
+    if ((r & XFCE_RR_ROTATION_270)		||
+	(r & XFCE_RR_ROTATION_90))
+    {
+	return TRUE;
+    }
+
+    return FALSE;
+}
+
+static void
+accumulate_error (GString *accumulated_error, GError *error)
+{
+    g_string_append_printf (accumulated_error, "    %s\n", error->message);
+    g_error_free (error);
+}
+
+/* Check whether the given set of settings can be used
+ * at the same time -- ie. whether there is an assignment
+ * of CRTC's to outputs.
+ *
+ * Brute force - the number of objects involved is small
+ * enough that it doesn't matter.
+ */
+static gboolean
+real_assign_crtcs (XfceRRScreen *screen,
+		   XfceRROutputInfo **outputs,
+		   CrtcAssignment *assignment,
+		   GError **error)
+{
+    XfceRRCrtc **crtcs = xfce_rr_screen_list_crtcs (screen);
+    XfceRROutputInfo *output;
+    int i;
+    gboolean tried_mode;
+    GError *my_error;
+    GString *accumulated_error;
+    gboolean success;
+
+    output = *outputs;
+    if (!output)
+	return TRUE;
+
+    /* It is always allowed for an output to be turned off */
+    if (!output->priv->on)
+    {
+	return real_assign_crtcs (screen, outputs + 1, assignment, error);
+    }
+
+    success = FALSE;
+    tried_mode = FALSE;
+    accumulated_error = g_string_new (NULL);
+
+    for (i = 0; crtcs[i] != NULL; ++i)
+    {
+	XfceRRCrtc *crtc = crtcs[i];
+	int crtc_id = xfce_rr_crtc_get_id (crtc);
+	int pass;
+
+	g_string_append_printf (accumulated_error,
+				_("Trying modes for CRTC %d\n"),
+				crtc_id);
+
+	/* Make two passes, one where frequencies must match, then
+	 * one where they don't have to
+	 */
+	for (pass = 0; pass < 2; ++pass)
+	{
+	    XfceRROutput *xfce_rr_output = xfce_rr_screen_get_output_by_name (screen, output->priv->name);
+	    XfceRRMode **modes = xfce_rr_output_list_modes (xfce_rr_output);
+	    int j;
+
+	    for (j = 0; modes[j] != NULL; ++j)
+	    {
+		XfceRRMode *mode = modes[j];
+		int mode_width;
+		int mode_height;
+		int mode_freq;
+
+		mode_width = xfce_rr_mode_get_width (mode);
+		mode_height = xfce_rr_mode_get_height (mode);
+		mode_freq = xfce_rr_mode_get_freq (mode);
+
+		g_string_append_printf (accumulated_error,
+					_("CRTC %d: trying mode %dx%d@%dHz with output at %dx%d@%dHz (pass %d)\n"),
+					crtc_id,
+					mode_width, mode_height, mode_freq,
+					output->priv->width, output->priv->height, output->priv->rate,
+					pass);
+
+		if (mode_width == output->priv->width	&&
+		    mode_height == output->priv->height &&
+		    (pass == 1 || mode_freq == output->priv->rate))
+		{
+		    tried_mode = TRUE;
+
+		    my_error = NULL;
+		    if (crtc_assignment_assign (
+			    assignment, crtc, modes[j],
+			    output->priv->x, output->priv->y,
+			    output->priv->rotation,
+                            output->priv->primary,
+			    xfce_rr_output,
+			    &my_error))
+		    {
+			my_error = NULL;
+			if (real_assign_crtcs (screen, outputs + 1, assignment, &my_error)) {
+			    success = TRUE;
+			    goto out;
+			} else
+			    accumulate_error (accumulated_error, my_error);
+
+			crtc_assignment_unassign (assignment, crtc, xfce_rr_output);
+		    } else
+			accumulate_error (accumulated_error, my_error);
+		}
+	    }
+	}
+    }
+
+out:
+
+    if (success)
+	g_string_free (accumulated_error, TRUE);
+    else {
+	char *str;
+
+	str = g_string_free (accumulated_error, FALSE);
+
+	if (tried_mode)
+	    g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_CRTC_ASSIGNMENT,
+			 _("could not assign CRTCs to outputs:\n%s"),
+			 str);
+	else
+	    g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_CRTC_ASSIGNMENT,
+			 _("none of the selected modes were compatible with the possible modes:\n%s"),
+			 str);
+
+	g_free (str);
+    }
+
+    return success;
+}
+
+static void
+crtc_info_free (CrtcInfo *info)
+{
+    g_ptr_array_free (info->outputs, TRUE);
+    g_free (info);
+}
+
+static void
+get_required_virtual_size (CrtcAssignment *assign, int *width, int *height)
+{
+    GList *active_crtcs = g_hash_table_get_keys (assign->info);
+    GList *list;
+    int d;
+
+    if (!width)
+	width = &d;
+    if (!height)
+	height = &d;
+    
+    /* Compute size of the screen */
+    *width = *height = 1;
+    for (list = active_crtcs; list != NULL; list = list->next)
+    {
+	XfceRRCrtc *crtc = list->data;
+	CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+	int w, h;
+
+	w = xfce_rr_mode_get_width (info->mode);
+	h = xfce_rr_mode_get_height (info->mode);
+	
+	if (mode_is_rotated (info))
+	{
+	    int tmp = h;
+	    h = w;
+	    w = tmp;
+	}
+	
+	*width = MAX (*width, info->x + w);
+	*height = MAX (*height, info->y + h);
+    }
+
+    g_list_free (active_crtcs);
+}
+
+static CrtcAssignment *
+crtc_assignment_new (XfceRRScreen *screen, XfceRROutputInfo **outputs, GError **error)
+{
+    CrtcAssignment *assignment = g_new0 (CrtcAssignment, 1);
+
+    assignment->info = g_hash_table_new_full (
+	g_direct_hash, g_direct_equal, NULL, (GFreeFunc)crtc_info_free);
+
+    if (real_assign_crtcs (screen, outputs, assignment, error))
+    {
+	int width, height;
+	int min_width, max_width, min_height, max_height;
+	int required_pixels, min_pixels, max_pixels;
+
+	get_required_virtual_size (assignment, &width, &height);
+
+	xfce_rr_screen_get_ranges (
+	    screen, &min_width, &max_width, &min_height, &max_height);
+
+	required_pixels = width * height;
+	min_pixels = min_width * min_height;
+	max_pixels = max_width * max_height;
+
+	if (required_pixels < min_pixels || required_pixels > max_pixels)
+	{
+	    g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_BOUNDS_ERROR,
+			 /* Translators: the "requested", "minimum", and
+			  * "maximum" words here are not keywords; please
+			  * translate them as usual. */
+			 _("required virtual size does not fit available size: "
+			   "requested=(%d, %d), minimum=(%d, %d), maximum=(%d, %d)"),
+			 width, height,
+			 min_width, min_height,
+			 max_width, max_height);
+	    goto fail;
+	}
+
+	assignment->screen = screen;
+	
+	return assignment;
+    }
+
+fail:
+    crtc_assignment_free (assignment);
+    
+    return NULL;
+}
+
+static gboolean
+crtc_assignment_apply (CrtcAssignment *assign, guint32 timestamp, GError **error)
+{
+    XfceRRCrtc **all_crtcs = xfce_rr_screen_list_crtcs (assign->screen);
+    int width, height;
+    int i;
+    int min_width, max_width, min_height, max_height;
+    int width_mm, height_mm;
+    gboolean success = TRUE;
+
+    /* Compute size of the screen */
+    get_required_virtual_size (assign, &width, &height);
+
+    xfce_rr_screen_get_ranges (
+	assign->screen, &min_width, &max_width, &min_height, &max_height);
+
+    /* We should never get here if the dimensions don't fit in the virtual size,
+     * but just in case we do, fix it up.
+     */
+    width = MAX (min_width, width);
+    width = MIN (max_width, width);
+    height = MAX (min_height, height);
+    height = MIN (max_height, height);
+
+    /* FMQ: do we need to check the sizes instead of clamping them? */
+
+    /* Grab the server while we fiddle with the CRTCs and the screen, so that
+     * apps that listen for RANDR notifications will only receive the final
+     * status.
+     */
+
+    gdk_x11_display_grab (gdk_screen_get_display (assign->screen->priv->gdk_screen));
+
+    /* Turn off all crtcs that are currently displaying outside the new screen,
+     * or are not used in the new setup
+     */
+    for (i = 0; all_crtcs[i] != NULL; ++i)
+    {
+	XfceRRCrtc *crtc = all_crtcs[i];
+	XfceRRMode *mode = xfce_rr_crtc_get_current_mode (crtc);
+	int x, y;
+
+	if (mode)
+	{
+	    int w, h;
+	    xfce_rr_crtc_get_position (crtc, &x, &y);
+
+	    w = xfce_rr_mode_get_width (mode);
+	    h = xfce_rr_mode_get_height (mode);
+
+	    if (crtc_is_rotated (crtc))
+	    {
+		int tmp = h;
+		h = w;
+		w = tmp;
+	    }
+	    
+	    if (x + w > width || y + h > height || !g_hash_table_lookup (assign->info, crtc))
+	    {
+		if (!xfce_rr_crtc_set_config_with_time (crtc, timestamp, 0, 0, NULL, XFCE_RR_ROTATION_0, NULL, 0, error))
+		{
+		    success = FALSE;
+		    break;
+		}
+		
+	    }
+	}
+    }
+
+    /* The 'physical size' of an X screen is meaningless if that screen
+     * can consist of many monitors. So just pick a size that make the
+     * dpi 96.
+     *
+     * Firefox and Evince apparently believe what X tells them.
+     */
+    width_mm = (width / 96.0) * 25.4 + 0.5;
+    height_mm = (height / 96.0) * 25.4 + 0.5;
+
+    if (success)
+    {
+	ConfigureCrtcState state;
+
+	xfce_rr_screen_set_size (assign->screen, width, height, width_mm, height_mm);
+
+	state.timestamp = timestamp;
+	state.has_error = FALSE;
+	state.error = error;
+	
+	g_hash_table_foreach (assign->info, configure_crtc, &state);
+
+	success = !state.has_error;
+    }
+
+    xfce_rr_screen_set_primary_output (assign->screen, assign->primary);
+
+    gdk_x11_display_ungrab (gdk_screen_get_display (assign->screen->priv->gdk_screen));
+
+    return success;
+}
diff --git a/src/xfce-rr-config.h b/src/xfce-rr-config.h
new file mode 100644
index 0000000..7bf17a2
--- /dev/null
+++ b/src/xfce-rr-config.h
@@ -0,0 +1,147 @@
+/* xfce-rr-config.h
+ * -*- c-basic-offset: 4 -*-
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ * Copyright 2010 Giovanni Campagna
+ * 
+ * This file is part of the Mate Library.
+ * 
+ * The Mate Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Mate 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
+ * Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Mate Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ * 
+ * Author: Soren Sandmann <sandmann at redhat.com>
+ */
+#ifndef XFCE_RR_CONFIG_H
+#define XFCE_RR_CONFIG_H
+
+#include "xfce-rr.h"
+#include <glib.h>
+#include <glib-object.h>
+
+typedef struct XfceRROutputInfoPrivate XfceRROutputInfoPrivate;
+typedef struct XfceRRConfigPrivate XfceRRConfigPrivate;
+
+typedef struct
+{
+    GObject parent;
+
+    /*< private >*/
+    XfceRROutputInfoPrivate *priv;
+} XfceRROutputInfo;
+
+typedef struct
+{
+    GObjectClass parent_class;
+} XfceRROutputInfoClass;
+
+#define XFCE_TYPE_RR_OUTPUT_INFO                  (xfce_rr_output_info_get_type())
+#define XFCE_RR_OUTPUT_INFO(obj)                  (G_TYPE_CHECK_INSTANCE_CAST ((obj), XFCE_TYPE_RR_OUTPUT_INFO, XfceRROutputInfo))
+#define XFCE_IS_RR_OUTPUT_INFO(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), XFCE_TYPE_RR_OUTPUT_INFO))
+#define XFCE_RR_OUTPUT_INFO_CLASS(klass)          (G_TYPE_CHECK_CLASS_CAST ((klass), XFCE_TYPE_RR_OUTPUT_INFO, XfceRROutputInfoClass))
+#define XFCE_IS_RR_OUTPUT_INFO_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass), XFCE_TYPE_RR_OUTPUT_INFO))
+#define XFCE_RR_OUTPUT_INFO_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), XFCE_TYPE_RR_OUTPUT_INFO, XfceRROutputInfoClass))
+
+GType xfce_rr_output_info_get_type (void);
+
+char *xfce_rr_output_info_get_name (XfceRROutputInfo *self);
+
+gboolean xfce_rr_output_info_is_active  (XfceRROutputInfo *self);
+void     xfce_rr_output_info_set_active (XfceRROutputInfo *self, gboolean active);
+
+
+void xfce_rr_output_info_get_geometry (XfceRROutputInfo *self, int *x, int *y, int *width, int *height);
+void xfce_rr_output_info_set_geometry (XfceRROutputInfo *self, int  x, int  y, int  width, int  height);
+
+int  xfce_rr_output_info_get_refresh_rate (XfceRROutputInfo *self);
+void xfce_rr_output_info_set_refresh_rate (XfceRROutputInfo *self, int rate);
+
+XfceRRRotation xfce_rr_output_info_get_rotation (XfceRROutputInfo *self);
+void            xfce_rr_output_info_set_rotation (XfceRROutputInfo *self, XfceRRRotation rotation);
+
+gboolean xfce_rr_output_info_is_connected     (XfceRROutputInfo *self);
+void     xfce_rr_output_info_get_vendor       (XfceRROutputInfo *self, gchar* vendor);
+guint    xfce_rr_output_info_get_product      (XfceRROutputInfo *self);
+guint    xfce_rr_output_info_get_serial       (XfceRROutputInfo *self);
+double   xfce_rr_output_info_get_aspect_ratio (XfceRROutputInfo *self);
+char    *xfce_rr_output_info_get_display_name (XfceRROutputInfo *self);
+
+gboolean xfce_rr_output_info_get_primary (XfceRROutputInfo *self);
+void     xfce_rr_output_info_set_primary (XfceRROutputInfo *self, gboolean primary);
+
+int xfce_rr_output_info_get_preferred_width  (XfceRROutputInfo *self);
+int xfce_rr_output_info_get_preferred_height (XfceRROutputInfo *self);
+
+typedef struct
+{
+    GObject parent;
+
+    /*< private >*/
+    XfceRRConfigPrivate *priv;
+} XfceRRConfig;
+
+typedef struct
+{
+    GObjectClass parent_class;
+} XfceRRConfigClass;
+
+#define XFCE_TYPE_RR_CONFIG                  (xfce_rr_config_get_type())
+#define XFCE_RR_CONFIG(obj)                  (G_TYPE_CHECK_INSTANCE_CAST ((obj), XFCE_TYPE_RR_CONFIG, XfceRRConfig))
+#define XFCE_IS_RR_CONFIG(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), XFCE_TYPE_RR_CONFIG))
+#define XFCE_RR_CONFIG_CLASS(klass)          (G_TYPE_CHECK_CLASS_CAST ((klass), XFCE_TYPE_RR_CONFIG, XfceRRConfigClass))
+#define XFCE_IS_RR_CONFIG_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass), XFCE_TYPE_RR_CONFIG))
+#define XFCE_RR_CONFIG_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), XFCE_TYPE_RR_CONFIG, XfceRRConfigClass))
+
+GType               xfce_rr_config_get_type     (void);
+
+XfceRRConfig      *xfce_rr_config_new_current  (XfceRRScreen  *screen,
+						  GError        **error);
+XfceRRConfig      *xfce_rr_config_new_stored   (XfceRRScreen  *screen,
+						  GError        **error);
+gboolean                xfce_rr_config_load_current (XfceRRConfig  *self,
+						      GError        **error);
+gboolean                xfce_rr_config_load_filename (XfceRRConfig  *self,
+						       const gchar    *filename,
+						       GError        **error);
+gboolean            xfce_rr_config_match        (XfceRRConfig  *config1,
+						  XfceRRConfig  *config2);
+gboolean            xfce_rr_config_equal	 (XfceRRConfig  *config1,
+						  XfceRRConfig  *config2);
+gboolean            xfce_rr_config_save         (XfceRRConfig  *configuration,
+						  GError        **error);
+void                xfce_rr_config_sanitize     (XfceRRConfig  *configuration);
+gboolean            xfce_rr_config_ensure_primary (XfceRRConfig  *configuration);
+
+gboolean	    xfce_rr_config_apply_with_time (XfceRRConfig  *configuration,
+						     XfceRRScreen  *screen,
+						     guint32         timestamp,
+						     GError        **error);
+
+gboolean            xfce_rr_config_apply_from_filename_with_time (XfceRRScreen  *screen,
+								   const char     *filename,
+								   guint32         timestamp,
+								   GError        **error);
+
+gboolean            xfce_rr_config_applicable   (XfceRRConfig  *configuration,
+						  XfceRRScreen  *screen,
+						  GError        **error);
+
+gboolean            xfce_rr_config_get_clone    (XfceRRConfig  *configuration);
+void                xfce_rr_config_set_clone    (XfceRRConfig  *configuration, gboolean clone);
+XfceRROutputInfo **xfce_rr_config_get_outputs  (XfceRRConfig  *configuration);
+
+char *xfce_rr_config_get_backup_filename (void);
+char *xfce_rr_config_get_intended_filename (void);
+
+#endif
diff --git a/src/xfce-rr-labeler.c b/src/xfce-rr-labeler.c
new file mode 100644
index 0000000..7a6fd6f
--- /dev/null
+++ b/src/xfce-rr-labeler.c
@@ -0,0 +1,550 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * xfce-rr-labeler.c - Utility to label monitors to identify them
+ * while they are being configured.
+ *
+ * Copyright 2008, Novell, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Mate 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Mate Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Federico Mena-Quintero <federico at novell.com>
+ */
+
+
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include <X11/Xproto.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <gdk/gdkx.h>
+
+#include "xfce-rr-labeler.h"
+
+struct _XfceRRLabelerPrivate {
+	XfceRRConfig *config;
+
+	int num_outputs;
+
+	GdkRGBA *palette;
+	GtkWidget **windows;
+
+	GdkScreen  *screen;
+	Atom        workarea_atom;
+};
+
+enum {
+	PROP_0,
+	PROP_CONFIG,
+	PROP_LAST
+};
+
+G_DEFINE_TYPE (XfceRRLabeler, xfce_rr_labeler, G_TYPE_OBJECT);
+
+static void xfce_rr_labeler_finalize (GObject *object);
+static void create_label_windows (XfceRRLabeler *labeler);
+static void setup_from_config (XfceRRLabeler *labeler);
+
+static int
+get_current_desktop (GdkScreen *screen)
+{
+        Display *display;
+        Window win;
+        Atom current_desktop, type;
+        int format;
+        unsigned long n_items, bytes_after;
+        unsigned char *data_return = NULL;
+        int workspace = 0;
+
+        display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
+        win = XRootWindow (display, GDK_SCREEN_XNUMBER (screen));
+
+        current_desktop = XInternAtom (display, "_NET_CURRENT_DESKTOP", True);
+
+        XGetWindowProperty (display,
+                            win,
+                            current_desktop,
+                            0, G_MAXLONG,
+                            False, XA_CARDINAL,
+                            &type, &format, &n_items, &bytes_after,
+                            &data_return);
+
+        if (type == XA_CARDINAL && format == 32 && n_items > 0)
+                workspace = (int) data_return[0];
+        if (data_return)
+                XFree (data_return);
+
+        return workspace;
+}
+
+static gboolean
+get_work_area (XfceRRLabeler *labeler,
+	       GdkRectangle   *rect)
+{
+	Atom            workarea;
+	Atom            type;
+	Window          win;
+	int             format;
+	gulong          num;
+	gulong          leftovers;
+	gulong          max_len = 4 * 32;
+	guchar         *ret_workarea;
+	long           *workareas;
+	int             result;
+	int             disp_screen;
+	int             desktop;
+	Display        *display;
+
+	display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (labeler->priv->screen));
+	workarea = XInternAtom (display, "_NET_WORKAREA", True);
+
+	disp_screen = GDK_SCREEN_XNUMBER (labeler->priv->screen);
+
+	/* Defaults in case of error */
+	rect->x = 0;
+	rect->y = 0;
+	rect->width = WidthOfScreen (gdk_x11_screen_get_xscreen (labeler->priv->screen));
+	rect->height = HeightOfScreen (gdk_x11_screen_get_xscreen (labeler->priv->screen));
+
+	if (workarea == None)
+		return FALSE;
+
+	win = XRootWindow (display, disp_screen);
+	result = XGetWindowProperty (display,
+				     win,
+				     workarea,
+				     0,
+				     max_len,
+				     False,
+				     AnyPropertyType,
+				     &type,
+				     &format,
+				     &num,
+				     &leftovers,
+				     &ret_workarea);
+
+	if (result != Success
+	    || type == None
+	    || format == 0
+	    || leftovers
+	    || num % 4) {
+		return FALSE;
+	}
+
+	desktop = get_current_desktop (labeler->priv->screen);
+
+	workareas = (long *) ret_workarea;
+	rect->x = workareas[desktop * 4];
+	rect->y = workareas[desktop * 4 + 1];
+	rect->width = workareas[desktop * 4 + 2];
+	rect->height = workareas[desktop * 4 + 3];
+
+	XFree (ret_workarea);
+
+	return TRUE;
+}
+
+static GdkFilterReturn
+screen_xevent_filter (GdkXEvent      *xevent,
+		      GdkEvent       *event,
+		      XfceRRLabeler *labeler)
+{
+	XEvent *xev;
+
+	xev = (XEvent *) xevent;
+
+	if (xev->type == PropertyNotify &&
+	    xev->xproperty.atom == labeler->priv->workarea_atom) {
+		/* update label positions */
+		xfce_rr_labeler_hide (labeler);
+		create_label_windows (labeler);
+	}
+
+	return GDK_FILTER_CONTINUE;
+}
+
+static void
+xfce_rr_labeler_init (XfceRRLabeler *labeler)
+{
+	GdkWindow *gdkwindow;
+
+	labeler->priv = G_TYPE_INSTANCE_GET_PRIVATE (labeler, XFCE_TYPE_RR_LABELER, XfceRRLabelerPrivate);
+
+	labeler->priv->workarea_atom = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+						    "_NET_WORKAREA",
+						    True);
+
+	labeler->priv->screen = gdk_screen_get_default ();
+	/* code is not really designed to handle multiple screens so *shrug* */
+	gdkwindow = gdk_screen_get_root_window (labeler->priv->screen);
+	gdk_window_add_filter (gdkwindow, (GdkFilterFunc) screen_xevent_filter, labeler);
+	gdk_window_set_events (gdkwindow, gdk_window_get_events (gdkwindow) | GDK_PROPERTY_CHANGE_MASK);
+}
+
+static void
+xfce_rr_labeler_set_property (GObject *gobject, guint property_id, const GValue *value, GParamSpec *param_spec)
+{
+	XfceRRLabeler *self = XFCE_RR_LABELER (gobject);
+
+	switch (property_id) {
+	case PROP_CONFIG:
+		self->priv->config = XFCE_RR_CONFIG (g_value_dup_object (value));
+		return;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, param_spec);
+	}
+}
+
+static GObject *
+xfce_rr_labeler_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties)
+{
+	XfceRRLabeler *self = (XfceRRLabeler*) G_OBJECT_CLASS (xfce_rr_labeler_parent_class)->constructor (type, n_construct_properties, construct_properties);
+
+	setup_from_config (self);
+
+	return (GObject*) self;
+}
+
+static void
+xfce_rr_labeler_class_init (XfceRRLabelerClass *klass)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (klass, sizeof (XfceRRLabelerPrivate));
+
+	object_class = (GObjectClass *) klass;
+
+	object_class->set_property = xfce_rr_labeler_set_property;
+	object_class->finalize = xfce_rr_labeler_finalize;
+	object_class->constructor = xfce_rr_labeler_constructor;
+
+	g_object_class_install_property (object_class, PROP_CONFIG, g_param_spec_object ("config",
+											 "Configuration",
+											 "RandR configuration to label",
+											 XFCE_TYPE_RR_CONFIG,
+											 G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+											 G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
+}
+
+static void
+xfce_rr_labeler_finalize (GObject *object)
+{
+	XfceRRLabeler *labeler;
+	GdkWindow      *gdkwindow;
+
+	labeler = XFCE_RR_LABELER (object);
+
+	gdkwindow = gdk_screen_get_root_window (labeler->priv->screen);
+	gdk_window_remove_filter (gdkwindow, (GdkFilterFunc) screen_xevent_filter, labeler);
+
+	if (labeler->priv->config != NULL) {
+		g_object_unref (labeler->priv->config);
+	}
+
+	if (labeler->priv->windows != NULL) {
+		xfce_rr_labeler_hide (labeler);
+		g_free (labeler->priv->windows);
+	}
+
+	g_free (labeler->priv->palette);
+
+	G_OBJECT_CLASS (xfce_rr_labeler_parent_class)->finalize (object);
+}
+
+static int
+count_outputs (XfceRRConfig *config)
+{
+	int i;
+	XfceRROutputInfo **outputs = xfce_rr_config_get_outputs (config);
+
+	for (i = 0; outputs[i] != NULL; i++)
+		;
+
+	return i;
+}
+
+static void
+make_palette (XfceRRLabeler *labeler)
+{
+	/* The idea is that we go around an hue color wheel.  We want to start
+	 * at red, go around to green/etc. and stop at blue --- because magenta
+	 * is evil.  Eeeeek, no magenta, please!
+	 *
+	 * Purple would be nice, though.  Remember that we are watered down
+	 * (i.e. low saturation), so that would be like Like berries with cream.
+	 * Mmmmm, berries.
+	 */
+	double start_hue;
+	double end_hue;
+	int i;
+
+	g_assert (labeler->priv->num_outputs > 0);
+
+	labeler->priv->palette = g_new (GdkRGBA, labeler->priv->num_outputs);
+
+	start_hue = 0.0; /* red */
+	end_hue   = 2.0/3; /* blue */
+
+	for (i = 0; i < labeler->priv->num_outputs; i++) {
+		double h, s, v;
+		double r, g, b;
+
+		h = start_hue + (end_hue - start_hue) / labeler->priv->num_outputs * i;
+		s = 1.0 / 3;
+		v = 1.0;
+
+		gtk_hsv_to_rgb (h, s, v, &r, &g, &b);
+
+		labeler->priv->palette[i].red   = r;
+		labeler->priv->palette[i].green = g;
+		labeler->priv->palette[i].blue  = b;
+		labeler->priv->palette[i].alpha  = 1.0;
+	}
+}
+
+#define LABEL_WINDOW_EDGE_THICKNESS 2
+#define LABEL_WINDOW_PADDING 12
+
+static gboolean
+label_window_draw_event_cb (GtkWidget *widget, cairo_t *cr, gpointer data)
+{
+	GdkRGBA *color;
+	GtkAllocation allocation;
+
+	color = g_object_get_data (G_OBJECT (widget), "color");
+	gtk_widget_get_allocation (widget, &allocation);
+
+	/* edge outline */
+
+	cairo_set_source_rgb (cr, 0, 0, 0);
+	cairo_rectangle (cr,
+			 LABEL_WINDOW_EDGE_THICKNESS / 2.0,
+			 LABEL_WINDOW_EDGE_THICKNESS / 2.0,
+			 allocation.width - LABEL_WINDOW_EDGE_THICKNESS,
+			 allocation.height - LABEL_WINDOW_EDGE_THICKNESS);
+	cairo_set_line_width (cr, LABEL_WINDOW_EDGE_THICKNESS);
+	cairo_stroke (cr);
+
+	/* fill */
+	gdk_cairo_set_source_rgba (cr, color);
+	cairo_rectangle (cr,
+			 LABEL_WINDOW_EDGE_THICKNESS,
+			 LABEL_WINDOW_EDGE_THICKNESS,
+			 allocation.width - LABEL_WINDOW_EDGE_THICKNESS * 2,
+			 allocation.height - LABEL_WINDOW_EDGE_THICKNESS * 2);
+	cairo_fill (cr);
+
+	return FALSE;
+}
+
+static void
+position_window (XfceRRLabeler  *labeler,
+		 GtkWidget       *window,
+		 int              x,
+		 int              y)
+{
+	GdkRectangle    workarea;
+	GdkRectangle    monitor;
+	GdkMonitor     *monitor_num;
+
+	get_work_area (labeler, &workarea);
+	monitor_num = gdk_display_get_monitor_at_point (gdk_screen_get_display (labeler->priv->screen), x, y);
+	gdk_monitor_get_geometry (monitor_num, &monitor);
+	gdk_rectangle_intersect (&monitor, &workarea, &workarea);
+
+	gtk_window_move (GTK_WINDOW (window), workarea.x, workarea.y);
+}
+
+static GtkWidget *
+create_label_window (XfceRRLabeler *labeler, XfceRROutputInfo *output, GdkRGBA *color)
+{
+	GtkWidget *window;
+	GtkWidget *widget;
+	char *str;
+	char *display_name;
+	GdkRGBA black = { 0, 0, 0, 1.0 };
+	int x,y;
+
+	window = gtk_window_new (GTK_WINDOW_POPUP);
+	gtk_widget_set_app_paintable (window, TRUE);
+
+	gtk_container_set_border_width (GTK_CONTAINER (window), LABEL_WINDOW_PADDING + LABEL_WINDOW_EDGE_THICKNESS);
+
+	/* This is semi-dangerous.  The color is part of the labeler->palette
+	 * array.  Note that in xfce_rr_labeler_finalize(), we are careful to
+	 * free the palette only after we free the windows.
+	 */
+	g_object_set_data (G_OBJECT (window), "color", color);
+
+	g_signal_connect (window, "draw",
+			  G_CALLBACK (label_window_draw_event_cb), labeler);
+
+	if (xfce_rr_config_get_clone (labeler->priv->config)) {
+		/* Keep this string in sync with mate-control-center/capplets/display/xrandr-capplet.c:get_display_name() */
+
+		/* Translators:  this is the feature where what you see on your laptop's
+		 * screen is the same as your external monitor.  Here, "Mirror" is being
+		 * used as an adjective, not as a verb.  For example, the Spanish
+		 * translation could be "Pantallas en Espejo", *not* "Espejar Pantallas".
+		 */
+		display_name = g_strdup_printf (_("Mirror Screens"));
+		str = g_strdup_printf ("<b>%s</b>", display_name);
+	} else {
+		display_name = g_strdup_printf ("<b>%s</b>\n<small>%s</small>", xfce_rr_output_info_get_display_name (output), xfce_rr_output_info_get_name (output));
+		str = g_strdup_printf ("%s", display_name);
+	}
+	g_free (display_name);
+
+	widget = gtk_label_new (NULL);
+	gtk_label_set_markup (GTK_LABEL (widget), str);
+	g_free (str);
+
+	/* Make the label explicitly black.  We don't want it to follow the
+	 * theme's colors, since the label is always shown against a light
+	 * pastel background.  See bgo#556050
+	 */
+	gtk_widget_override_color (widget, gtk_widget_get_state_flags (widget), &black);
+
+	gtk_container_add (GTK_CONTAINER (window), widget);
+
+	/* Should we center this at the top edge of the monitor, instead of using the upper-left corner? */
+	xfce_rr_output_info_get_geometry (output, &x, &y, NULL, NULL);
+	position_window (labeler, window, x, y);
+
+	gtk_widget_show_all (window);
+
+	return window;
+}
+
+static void
+create_label_windows (XfceRRLabeler *labeler)
+{
+	int i;
+	gboolean created_window_for_clone;
+	XfceRROutputInfo **outputs;
+
+	labeler->priv->windows = g_new (GtkWidget *, labeler->priv->num_outputs);
+
+	created_window_for_clone = FALSE;
+
+	outputs = xfce_rr_config_get_outputs (labeler->priv->config);
+
+	for (i = 0; i < labeler->priv->num_outputs; i++) {
+		if (!created_window_for_clone && xfce_rr_output_info_is_active (outputs[i])) {
+			labeler->priv->windows[i] = create_label_window (labeler, outputs[i], labeler->priv->palette + i);
+
+			if (xfce_rr_config_get_clone (labeler->priv->config))
+				created_window_for_clone = TRUE;
+		} else
+			labeler->priv->windows[i] = NULL;
+	}
+}
+
+static void
+setup_from_config (XfceRRLabeler *labeler)
+{
+	labeler->priv->num_outputs = count_outputs (labeler->priv->config);
+
+	make_palette (labeler);
+
+	create_label_windows (labeler);
+}
+
+/**
+ * xfce_rr_labeler_new:
+ * @config: Configuration of the screens to label
+ *
+ * Create a GUI element that will display colored labels on each connected monitor.
+ * This is useful when users are required to identify which monitor is which, e.g. for
+ * for configuring multiple monitors.
+ * The labels will be shown by default, use xfce_rr_labeler_hide to hide them.
+ *
+ * Returns: A new #XfceRRLabeler
+ */
+XfceRRLabeler *
+xfce_rr_labeler_new (XfceRRConfig *config)
+{
+	g_return_val_if_fail (XFCE_IS_RR_CONFIG (config), NULL);
+
+	return g_object_new (XFCE_TYPE_RR_LABELER, "config", config, NULL);
+}
+
+/**
+ * xfce_rr_labeler_hide:
+ * @labeler: A #XfceRRLabeler
+ *
+ * Hide ouput labels.
+ */
+void
+xfce_rr_labeler_hide (XfceRRLabeler *labeler)
+{
+	int i;
+	XfceRRLabelerPrivate *priv;
+
+	g_return_if_fail (XFCE_IS_RR_LABELER (labeler));
+
+	priv = labeler->priv;
+
+	if (priv->windows == NULL)
+		return;
+
+	for (i = 0; i < priv->num_outputs; i++)
+		if (priv->windows[i] != NULL) {
+			gtk_widget_destroy (priv->windows[i]);
+			priv->windows[i] = NULL;
+	}
+	g_free (priv->windows);
+	priv->windows = NULL;
+}
+
+/**
+ * xfce_rr_labeler_get_rgba_for_output:
+ * @labeler: A #XfceRRLabeler
+ * @output: Output device (i.e. monitor) to query
+ * @color_out: (out): Color of selected monitor.
+ *
+ * Get the color used for the label on a given output (monitor).
+ */
+void
+xfce_rr_labeler_get_rgba_for_output (XfceRRLabeler *labeler, XfceRROutputInfo *output, GdkRGBA *color_out)
+{
+	int i;
+	XfceRROutputInfo **outputs;
+
+	g_return_if_fail (XFCE_IS_RR_LABELER (labeler));
+	g_return_if_fail (XFCE_IS_RR_OUTPUT_INFO (output));
+	g_return_if_fail (color_out != NULL);
+
+	outputs = xfce_rr_config_get_outputs (labeler->priv->config);
+
+	for (i = 0; i < labeler->priv->num_outputs; i++)
+		if (outputs[i] == output) {
+			*color_out = labeler->priv->palette[i];
+			return;
+		}
+
+	g_warning ("trying to get the color for unknown MateOutputInfo %p; returning magenta!", output);
+
+	color_out->red   = 1.0;
+	color_out->green = 0.0;
+	color_out->blue  = 1.0;
+	color_out->alpha = 1.0;
+}
diff --git a/src/xfce-rr-labeler.h b/src/xfce-rr-labeler.h
new file mode 100644
index 0000000..96c641d
--- /dev/null
+++ b/src/xfce-rr-labeler.h
@@ -0,0 +1,61 @@
+/* xfce-rr-labeler.h - Utility to label monitors to identify them
+ * while they are being configured.
+ *
+ * Copyright 2008, Novell, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Mate 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Mate Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ *
+ * Author: Federico Mena-Quintero <federico at novell.com>
+ */
+
+#ifndef XFCE_RR_LABELER_H
+#define XFCE_RR_LABELER_H
+
+#include "xfce-rr-config.h"
+
+#define XFCE_TYPE_RR_LABELER            (xfce_rr_labeler_get_type ())
+#define XFCE_RR_LABELER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), XFCE_TYPE_RR_LABELER, XfceRRLabeler))
+#define XFCE_RR_LABELER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  XFCE_TYPE_RR_LABELER, XfceRRLabelerClass))
+#define XFCE_IS_RR_LABELER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), XFCE_TYPE_RR_LABELER))
+#define XFCE_IS_RR_LABELER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  XFCE_TYPE_RR_LABELER))
+#define XFCE_RR_LABELER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  XFCE_TYPE_RR_LABELER, XfceRRLabelerClass))
+
+typedef struct _XfceRRLabeler XfceRRLabeler;
+typedef struct _XfceRRLabelerClass XfceRRLabelerClass;
+typedef struct _XfceRRLabelerPrivate XfceRRLabelerPrivate;
+
+struct _XfceRRLabeler {
+	GObject parent;
+
+	/*< private >*/
+	XfceRRLabelerPrivate *priv;
+};
+
+struct _XfceRRLabelerClass {
+	GObjectClass parent_class;
+};
+
+GType xfce_rr_labeler_get_type (void);
+
+XfceRRLabeler *xfce_rr_labeler_new (XfceRRConfig *config);
+
+void xfce_rr_labeler_hide (XfceRRLabeler *labeler);
+
+void xfce_rr_labeler_get_rgba_for_output (XfceRRLabeler *labeler, XfceRROutputInfo *output, GdkRGBA *color_out);
+
+#endif
diff --git a/src/xfce-rr-output-info.c b/src/xfce-rr-output-info.c
new file mode 100644
index 0000000..c17e9db
--- /dev/null
+++ b/src/xfce-rr-output-info.c
@@ -0,0 +1,254 @@
+/* xfce-rr-output-info.c
+ * -*- c-basic-offset: 4 -*-
+ *
+ * Copyright 2010 Giovanni Campagna
+ *
+ * This file is part of the Mate Desktop Library.
+ *
+ * The Mate Desktop Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Mate 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Mate Desktop Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+
+#include <config.h>
+
+#include "xfce-rr-config.h"
+
+#include "edid.h"
+#include "xfce-rr-private.h"
+
+G_DEFINE_TYPE (XfceRROutputInfo, xfce_rr_output_info, G_TYPE_OBJECT)
+
+static void
+xfce_rr_output_info_init (XfceRROutputInfo *self)
+{
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, XFCE_TYPE_RR_OUTPUT_INFO, XfceRROutputInfoPrivate);
+
+    self->priv->name = NULL;
+    self->priv->on = FALSE;
+    self->priv->display_name = NULL;
+}
+
+static void
+xfce_rr_output_info_finalize (GObject *gobject)
+{
+    XfceRROutputInfo *self = XFCE_RR_OUTPUT_INFO (gobject);
+
+    g_free (self->priv->name);
+    g_free (self->priv->display_name);
+
+    G_OBJECT_CLASS (xfce_rr_output_info_parent_class)->finalize (gobject);
+}
+
+static void
+xfce_rr_output_info_class_init (XfceRROutputInfoClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    g_type_class_add_private (klass, sizeof (XfceRROutputInfoPrivate));
+
+    gobject_class->finalize = xfce_rr_output_info_finalize;
+}
+
+/**
+ * xfce_rr_output_info_get_name:
+ *
+ * Returns: (transfer none): the output name
+ */
+char *xfce_rr_output_info_get_name (XfceRROutputInfo *self)
+{
+    g_return_val_if_fail (XFCE_IS_RR_OUTPUT_INFO (self), NULL);
+
+    return self->priv->name;
+}
+
+/**
+ * xfce_rr_output_info_is_active:
+ *
+ * Returns: whether there is a CRTC assigned to this output (i.e. a signal is being sent to it)
+ */
+gboolean xfce_rr_output_info_is_active (XfceRROutputInfo *self)
+{
+    g_return_val_if_fail (XFCE_IS_RR_OUTPUT_INFO (self), FALSE);
+
+    return self->priv->on;
+}
+
+void xfce_rr_output_info_set_active (XfceRROutputInfo *self, gboolean active)
+{
+    g_return_if_fail (XFCE_IS_RR_OUTPUT_INFO (self));
+
+    self->priv->on = active;
+}
+
+/**
+ * xfce_rr_output_info_get_geometry:
+ * @self: a #XfceRROutputInfo
+ * @x: (out) (allow-none):
+ * @y: (out) (allow-none):
+ * @width: (out) (allow-none):
+ * @height: (out) (allow-none):
+ */
+void xfce_rr_output_info_get_geometry (XfceRROutputInfo *self, int *x, int *y, int *width, int *height)
+{
+    g_return_if_fail (XFCE_IS_RR_OUTPUT_INFO (self));
+
+    if (x)
+	*x = self->priv->x;
+    if (y)
+	*y = self->priv->y;
+    if (width)
+	*width = self->priv->width;
+    if (height)
+	*height = self->priv->height;
+}
+
+/**
+ * xfce_rr_output_info_set_geometry:
+ * @self: a #XfceRROutputInfo
+ * @x: x offset for monitor
+ * @y: y offset for monitor
+ * @width: monitor width
+ * @height: monitor height
+ *
+ * Set the geometry for the monitor connected to the specified output.
+ */
+void xfce_rr_output_info_set_geometry (XfceRROutputInfo *self, int  x, int  y, int  width, int  height)
+{
+    g_return_if_fail (XFCE_IS_RR_OUTPUT_INFO (self));
+
+    self->priv->x = x;
+    self->priv->y = y;
+    self->priv->width = width;
+    self->priv->height = height;
+}
+
+int xfce_rr_output_info_get_refresh_rate (XfceRROutputInfo *self)
+{
+    g_return_val_if_fail (XFCE_IS_RR_OUTPUT_INFO (self), 0);
+
+    return self->priv->rate;
+}
+
+void xfce_rr_output_info_set_refresh_rate (XfceRROutputInfo *self, int rate)
+{
+    g_return_if_fail (XFCE_IS_RR_OUTPUT_INFO (self));
+
+    self->priv->rate = rate;
+}
+
+XfceRRRotation xfce_rr_output_info_get_rotation (XfceRROutputInfo *self)
+{
+    g_return_val_if_fail (XFCE_IS_RR_OUTPUT_INFO (self), XFCE_RR_ROTATION_0);
+
+    return self->priv->rotation;
+}
+
+void xfce_rr_output_info_set_rotation (XfceRROutputInfo *self, XfceRRRotation rotation)
+{
+    g_return_if_fail (XFCE_IS_RR_OUTPUT_INFO (self));
+
+    self->priv->rotation = rotation;
+}
+
+/**
+ * xfce_rr_output_info_is_connected:
+ *
+ * Returns: whether the output is physically connected to a monitor
+ */
+gboolean xfce_rr_output_info_is_connected (XfceRROutputInfo *self)
+{
+    g_return_val_if_fail (XFCE_IS_RR_OUTPUT_INFO (self), FALSE);
+
+    return self->priv->connected;
+}
+
+/**
+ * xfce_rr_output_info_get_vendor:
+ * @self: a #XfceRROutputInfo
+ * @vendor: (out caller-allocates) (array fixed-size=4):
+ */
+void xfce_rr_output_info_get_vendor (XfceRROutputInfo *self, gchar* vendor)
+{
+    g_return_if_fail (XFCE_IS_RR_OUTPUT_INFO (self));
+    g_return_if_fail (vendor != NULL);
+
+    vendor[0] = self->priv->vendor[0];
+    vendor[1] = self->priv->vendor[1];
+    vendor[2] = self->priv->vendor[2];
+    vendor[3] = self->priv->vendor[3];
+}
+
+guint xfce_rr_output_info_get_product (XfceRROutputInfo *self)
+{
+    g_return_val_if_fail (XFCE_IS_RR_OUTPUT_INFO (self), 0);
+
+    return self->priv->product;
+}
+
+guint xfce_rr_output_info_get_serial (XfceRROutputInfo *self)
+{
+    g_return_val_if_fail (XFCE_IS_RR_OUTPUT_INFO (self), 0);
+
+    return self->priv->serial;
+}
+
+double xfce_rr_output_info_get_aspect_ratio (XfceRROutputInfo *self)
+{
+    g_return_val_if_fail (XFCE_IS_RR_OUTPUT_INFO (self), 0);
+
+    return self->priv->aspect;
+}
+
+/**
+ * xfce_rr_output_info_get_display_name:
+ *
+ * Returns: (transfer none): the display name of this output
+ */
+char *xfce_rr_output_info_get_display_name (XfceRROutputInfo *self)
+{
+    g_return_val_if_fail (XFCE_IS_RR_OUTPUT_INFO (self), NULL);
+
+    return self->priv->display_name;
+}
+
+gboolean xfce_rr_output_info_get_primary (XfceRROutputInfo *self)
+{
+    g_return_val_if_fail (XFCE_IS_RR_OUTPUT_INFO (self), FALSE);
+
+    return self->priv->primary;
+}
+
+void xfce_rr_output_info_set_primary (XfceRROutputInfo *self, gboolean primary)
+{
+    g_return_if_fail (XFCE_IS_RR_OUTPUT_INFO (self));
+
+    self->priv->primary = primary;
+}
+
+int xfce_rr_output_info_get_preferred_width (XfceRROutputInfo *self)
+{
+    g_return_val_if_fail (XFCE_IS_RR_OUTPUT_INFO (self), 0);
+
+    return self->priv->pref_width;
+}
+
+int xfce_rr_output_info_get_preferred_height (XfceRROutputInfo *self)
+{
+    g_return_val_if_fail (XFCE_IS_RR_OUTPUT_INFO (self), 0);
+
+    return self->priv->pref_height;
+}
diff --git a/src/xfce-rr-private.h b/src/xfce-rr-private.h
new file mode 100644
index 0000000..f5b613b
--- /dev/null
+++ b/src/xfce-rr-private.h
@@ -0,0 +1,84 @@
+#ifndef XFCE_RR_PRIVATE_H
+#define XFCE_RR_PRIVATE_H
+
+#include <X11/Xlib.h>
+
+#ifdef HAVE_RANDR
+#include <X11/extensions/Xrandr.h>
+#endif
+
+typedef struct ScreenInfo ScreenInfo;
+
+struct ScreenInfo
+{
+    int			min_width;
+    int			max_width;
+    int			min_height;
+    int			max_height;
+
+#ifdef HAVE_RANDR
+    XRRScreenResources *resources;
+#endif
+    
+    XfceRROutput **	outputs;
+    XfceRRCrtc **	crtcs;
+    XfceRRMode **	modes;
+    
+    XfceRRScreen *	screen;
+
+    XfceRRMode **	clone_modes;
+
+#ifdef HAVE_RANDR
+    RROutput            primary;
+#endif
+};
+
+struct XfceRRScreenPrivate
+{
+    GdkScreen *			gdk_screen;
+    GdkWindow *			gdk_root;
+    Display *			xdisplay;
+    Screen *			xscreen;
+    Window			xroot;
+    ScreenInfo *		info;
+    
+    int				randr_event_base;
+    int				rr_major_version;
+    int				rr_minor_version;
+    
+    Atom                        connector_type_atom;
+};
+
+struct XfceRROutputInfoPrivate
+{
+    char *		name;
+
+    gboolean		on;
+    int			width;
+    int			height;
+    int			rate;
+    int			x;
+    int			y;
+    XfceRRRotation	rotation;
+
+    gboolean		connected;
+    gchar		vendor[4];
+    guint		product;
+    guint		serial;
+    double		aspect;
+    int			pref_width;
+    int			pref_height;
+    char *		display_name;
+    gboolean            primary;
+};
+
+struct XfceRRConfigPrivate
+{
+  gboolean clone;
+  XfceRRScreen *screen;
+  XfceRROutputInfo **outputs;
+};
+
+gboolean _xfce_rr_output_name_is_laptop (const char *name);
+
+#endif
diff --git a/src/xfce-rr.c b/src/xfce-rr.c
new file mode 100644
index 0000000..02dc786
--- /dev/null
+++ b/src/xfce-rr.c
@@ -0,0 +1,2164 @@
+/* xfce-rr.c
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ * 
+ * This file is part of the Mate Library.
+ * 
+ * The Mate Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Mate 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
+ * Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Mate Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ * 
+ * Author: Soren Sandmann <sandmann at redhat.com>
+ */
+
+
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include <X11/Xlib.h>
+
+#ifdef HAVE_RANDR
+#include <X11/extensions/Xrandr.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+
+#undef MATE_DISABLE_DEPRECATED
+#include "xfce-rr.h"
+#include "xfce-rr-config.h"
+
+#include "xfce-rr-private.h"
+
+#define DISPLAY(o) ((o)->info->screen->priv->xdisplay)
+
+#ifndef HAVE_RANDR
+/* This is to avoid a ton of ifdefs wherever we use a type from libXrandr */
+typedef int RROutput;
+typedef int RRCrtc;
+typedef int RRMode;
+typedef int Rotation;
+#define RR_Rotate_0		1
+#define RR_Rotate_90		2
+#define RR_Rotate_180		4
+#define RR_Rotate_270		8
+#define RR_Reflect_X		16
+#define RR_Reflect_Y		32
+#endif
+
+enum {
+    SCREEN_PROP_0,
+    SCREEN_PROP_GDK_SCREEN,
+    SCREEN_PROP_LAST,
+};
+
+enum {
+    SCREEN_CHANGED,
+    SCREEN_SIGNAL_LAST,
+};
+
+gint screen_signals[SCREEN_SIGNAL_LAST];
+
+struct XfceRROutput
+{
+    ScreenInfo *	info;
+    RROutput		id;
+    
+    char *		name;
+    XfceRRCrtc *	current_crtc;
+    gboolean		connected;
+    gulong		width_mm;
+    gulong		height_mm;
+    XfceRRCrtc **	possible_crtcs;
+    XfceRROutput **	clones;
+    XfceRRMode **	modes;
+    int			n_preferred;
+    guint8 *		edid_data;
+    int         edid_size;
+    char *              connector_type;
+};
+
+struct XfceRROutputWrap
+{
+    RROutput		id;
+};
+
+struct XfceRRCrtc
+{
+    ScreenInfo *	info;
+    RRCrtc		id;
+    
+    XfceRRMode *	current_mode;
+    XfceRROutput **	current_outputs;
+    XfceRROutput **	possible_outputs;
+    int			x;
+    int			y;
+    
+    XfceRRRotation	current_rotation;
+    XfceRRRotation	rotations;
+    int			gamma_size;
+};
+
+struct XfceRRMode
+{
+    ScreenInfo *	info;
+    RRMode		id;
+    char *		name;
+    int			width;
+    int			height;
+    int			freq;		/* in mHz */
+};
+
+/* XfceRRCrtc */
+static XfceRRCrtc *  crtc_new          (ScreenInfo         *info,
+					 RRCrtc              id);
+static XfceRRCrtc *  crtc_copy         (const XfceRRCrtc  *from);
+static void           crtc_free         (XfceRRCrtc        *crtc);
+
+#ifdef HAVE_RANDR
+static gboolean       crtc_initialize   (XfceRRCrtc        *crtc,
+					 XRRScreenResources *res,
+					 GError            **error);
+#endif
+
+/* XfceRROutput */
+static XfceRROutput *output_new        (ScreenInfo         *info,
+					 RROutput            id);
+
+#ifdef HAVE_RANDR
+static gboolean       output_initialize (XfceRROutput      *output,
+					 XRRScreenResources *res,
+					 GError            **error);
+#endif
+
+static XfceRROutput *output_copy       (const XfceRROutput *from);
+static void           output_free       (XfceRROutput      *output);
+
+/* XfceRRMode */
+static XfceRRMode *  mode_new          (ScreenInfo         *info,
+					 RRMode              id);
+
+#ifdef HAVE_RANDR
+static void           mode_initialize   (XfceRRMode        *mode,
+					 XRRModeInfo        *info);
+#endif
+
+static XfceRRMode *  mode_copy         (const XfceRRMode  *from);
+static void           mode_free         (XfceRRMode        *mode);
+
+
+static void xfce_rr_screen_finalize (GObject*);
+static void xfce_rr_screen_set_property (GObject*, guint, const GValue*, GParamSpec*);
+static void xfce_rr_screen_get_property (GObject*, guint, GValue*, GParamSpec*);
+static gboolean xfce_rr_screen_initable_init (GInitable*, GCancellable*, GError**);
+static void xfce_rr_screen_initable_iface_init (GInitableIface *iface);
+G_DEFINE_TYPE_WITH_CODE (XfceRRScreen, xfce_rr_screen, G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, xfce_rr_screen_initable_iface_init))
+
+G_DEFINE_BOXED_TYPE (XfceRRCrtc, xfce_rr_crtc, crtc_copy, crtc_free)
+G_DEFINE_BOXED_TYPE (XfceRROutput, xfce_rr_output, output_copy, output_free)
+G_DEFINE_BOXED_TYPE (XfceRRMode, xfce_rr_mode, mode_copy, mode_free)
+
+/* Errors */
+
+/**
+ * xfce_rr_error_quark:
+ *
+ * Returns the #GQuark that will be used for #GError values returned by the
+ * XfceRR API.
+ *
+ * Return value: a #GQuark used to identify errors coming from the XfceRR API.
+ */
+GQuark
+xfce_rr_error_quark (void)
+{
+    return g_quark_from_static_string ("xfce-rr-error-quark");
+}
+
+/* Screen */
+static XfceRROutput *
+xfce_rr_output_by_id (ScreenInfo *info, RROutput id)
+{
+    XfceRROutput **output;
+    
+    g_assert (info != NULL);
+    
+    for (output = info->outputs; *output; ++output)
+    {
+	if ((*output)->id == id)
+	    return *output;
+    }
+    
+    return NULL;
+}
+
+static XfceRRCrtc *
+crtc_by_id (ScreenInfo *info, RRCrtc id)
+{
+    XfceRRCrtc **crtc;
+    
+    if (!info)
+        return NULL;
+    
+    for (crtc = info->crtcs; *crtc; ++crtc)
+    {
+	if ((*crtc)->id == id)
+	    return *crtc;
+    }
+    
+    return NULL;
+}
+
+static XfceRRMode *
+mode_by_id (ScreenInfo *info, RRMode id)
+{
+    XfceRRMode **mode;
+    
+    g_assert (info != NULL);
+    
+    for (mode = info->modes; *mode; ++mode)
+    {
+	if ((*mode)->id == id)
+	    return *mode;
+    }
+    
+    return NULL;
+}
+
+static void
+screen_info_free (ScreenInfo *info)
+{
+    XfceRROutput **output;
+    XfceRRCrtc **crtc;
+    XfceRRMode **mode;
+    
+    g_assert (info != NULL);
+
+#ifdef HAVE_RANDR
+    if (info->resources)
+    {
+	XRRFreeScreenResources (info->resources);
+	
+	info->resources = NULL;
+    }
+#endif
+    
+    if (info->outputs)
+    {
+	for (output = info->outputs; *output; ++output)
+	    output_free (*output);
+	g_free (info->outputs);
+    }
+    
+    if (info->crtcs)
+    {
+	for (crtc = info->crtcs; *crtc; ++crtc)
+	    crtc_free (*crtc);
+	g_free (info->crtcs);
+    }
+    
+    if (info->modes)
+    {
+	for (mode = info->modes; *mode; ++mode)
+	    mode_free (*mode);
+	g_free (info->modes);
+    }
+
+    if (info->clone_modes)
+    {
+	/* The modes themselves were freed above */
+	g_free (info->clone_modes);
+    }
+    
+    g_free (info);
+}
+
+static gboolean
+has_similar_mode (XfceRROutput *output, XfceRRMode *mode)
+{
+    int i;
+    XfceRRMode **modes = xfce_rr_output_list_modes (output);
+    int width = xfce_rr_mode_get_width (mode);
+    int height = xfce_rr_mode_get_height (mode);
+
+    for (i = 0; modes[i] != NULL; ++i)
+    {
+	XfceRRMode *m = modes[i];
+
+	if (xfce_rr_mode_get_width (m) == width	&&
+	    xfce_rr_mode_get_height (m) == height)
+	{
+	    return TRUE;
+	}
+    }
+
+    return FALSE;
+}
+
+static void
+gather_clone_modes (ScreenInfo *info)
+{
+    int i;
+    GPtrArray *result = g_ptr_array_new ();
+
+    for (i = 0; info->outputs[i] != NULL; ++i)
+    {
+	int j;
+	XfceRROutput *output1, *output2;
+
+	output1 = info->outputs[i];
+	
+	if (!output1->connected)
+	    continue;
+	
+	for (j = 0; output1->modes[j] != NULL; ++j)
+	{
+	    XfceRRMode *mode = output1->modes[j];
+	    gboolean valid;
+	    int k;
+
+	    valid = TRUE;
+	    for (k = 0; info->outputs[k] != NULL; ++k)
+	    {
+		output2 = info->outputs[k];
+		
+		if (!output2->connected)
+		    continue;
+		
+		if (!has_similar_mode (output2, mode))
+		{
+		    valid = FALSE;
+		    break;
+		}
+	    }
+
+	    if (valid)
+		g_ptr_array_add (result, mode);
+	}
+    }
+
+    g_ptr_array_add (result, NULL);
+    
+    info->clone_modes = (XfceRRMode **)g_ptr_array_free (result, FALSE);
+}
+
+#ifdef HAVE_RANDR
+static gboolean
+fill_screen_info_from_resources (ScreenInfo *info,
+				 XRRScreenResources *resources,
+				 GError **error)
+{
+    int i;
+    GPtrArray *a;
+    XfceRRCrtc **crtc;
+    XfceRROutput **output;
+
+    info->resources = resources;
+
+    /* We create all the structures before initializing them, so
+     * that they can refer to each other.
+     */
+    a = g_ptr_array_new ();
+    for (i = 0; i < resources->ncrtc; ++i)
+    {
+	XfceRRCrtc *crtc = crtc_new (info, resources->crtcs[i]);
+
+	g_ptr_array_add (a, crtc);
+    }
+    g_ptr_array_add (a, NULL);
+    info->crtcs = (XfceRRCrtc **)g_ptr_array_free (a, FALSE);
+
+    a = g_ptr_array_new ();
+    for (i = 0; i < resources->noutput; ++i)
+    {
+	XfceRROutput *output = output_new (info, resources->outputs[i]);
+
+	g_ptr_array_add (a, output);
+    }
+    g_ptr_array_add (a, NULL);
+    info->outputs = (XfceRROutput **)g_ptr_array_free (a, FALSE);
+
+    a = g_ptr_array_new ();
+    for (i = 0;  i < resources->nmode; ++i)
+    {
+	XfceRRMode *mode = mode_new (info, resources->modes[i].id);
+
+	g_ptr_array_add (a, mode);
+    }
+    g_ptr_array_add (a, NULL);
+    info->modes = (XfceRRMode **)g_ptr_array_free (a, FALSE);
+
+    /* Initialize */
+    for (crtc = info->crtcs; *crtc; ++crtc)
+    {
+	if (!crtc_initialize (*crtc, resources, error))
+	    return FALSE;
+    }
+
+    for (output = info->outputs; *output; ++output)
+    {
+	if (!output_initialize (*output, resources, error))
+	    return FALSE;
+    }
+
+    for (i = 0; i < resources->nmode; ++i)
+    {
+	XfceRRMode *mode = mode_by_id (info, resources->modes[i].id);
+
+	mode_initialize (mode, &(resources->modes[i]));
+    }
+
+    gather_clone_modes (info);
+
+    return TRUE;
+}
+#endif /* HAVE_RANDR */
+
+static gboolean
+fill_out_screen_info (Display *xdisplay,
+		      Window xroot,
+		      ScreenInfo *info,
+		      gboolean needs_reprobe,
+		      GError **error)
+{
+#ifdef HAVE_RANDR
+    XRRScreenResources *resources;
+	GdkDisplay *display;
+    
+    g_assert (xdisplay != NULL);
+    g_assert (info != NULL);
+
+    /* First update the screen resources */
+
+    if (needs_reprobe)
+        resources = XRRGetScreenResources (xdisplay, xroot);
+    else
+        resources = XRRGetScreenResourcesCurrent (xdisplay, xroot);
+
+    if (resources)
+    {
+	if (!fill_screen_info_from_resources (info, resources, error))
+	    return FALSE;
+    }
+    else
+    {
+	g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_RANDR_ERROR,
+		     /* Translators: a CRTC is a CRT Controller (this is X terminology). */
+		     _("could not get the screen resources (CRTCs, outputs, modes)"));
+	return FALSE;
+    }
+
+    /* Then update the screen size range.  We do this after XRRGetScreenResources() so that
+     * the X server will already have an updated view of the outputs.
+     */
+
+    if (needs_reprobe) {
+	gboolean success;
+
+	display = gdk_display_get_default ();
+    gdk_x11_display_error_trap_push (display);
+	success = XRRGetScreenSizeRange (xdisplay, xroot,
+					 &(info->min_width),
+					 &(info->min_height),
+					 &(info->max_width),
+					 &(info->max_height));
+	gdk_display_flush (display);
+	if (gdk_x11_display_error_trap_pop (display)) {
+	    g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_UNKNOWN,
+			 _("unhandled X error while getting the range of screen sizes"));
+	    return FALSE;
+	}
+
+	if (!success) {
+	    g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_RANDR_ERROR,
+			 _("could not get the range of screen sizes"));
+            return FALSE;
+        }
+    }
+    else
+    {
+        xfce_rr_screen_get_ranges (info->screen, 
+					 &(info->min_width),
+					 &(info->max_width),
+					 &(info->min_height),
+					 &(info->max_height));
+    }
+
+    info->primary = None;
+	display = gdk_display_get_default ();
+    gdk_x11_display_error_trap_push (display);
+    info->primary = XRRGetOutputPrimary (xdisplay, xroot);
+    gdk_x11_display_error_trap_pop_ignored (display);
+
+    return TRUE;
+#else
+    return FALSE;
+#endif /* HAVE_RANDR */
+}
+
+static ScreenInfo *
+screen_info_new (XfceRRScreen *screen, gboolean needs_reprobe, GError **error)
+{
+    ScreenInfo *info = g_new0 (ScreenInfo, 1);
+    XfceRRScreenPrivate *priv;
+
+    g_assert (screen != NULL);
+
+    priv = screen->priv;
+
+    info->outputs = NULL;
+    info->crtcs = NULL;
+    info->modes = NULL;
+    info->screen = screen;
+    
+    if (fill_out_screen_info (priv->xdisplay, priv->xroot, info, needs_reprobe, error))
+    {
+	return info;
+    }
+    else
+    {
+	screen_info_free (info);
+	return NULL;
+    }
+}
+
+static gboolean
+screen_update (XfceRRScreen *screen, gboolean force_callback, gboolean needs_reprobe, GError **error)
+{
+    ScreenInfo *info;
+    gboolean changed = FALSE;
+    
+    g_assert (screen != NULL);
+
+    info = screen_info_new (screen, needs_reprobe, error);
+    if (!info)
+	    return FALSE;
+
+#ifdef HAVE_RANDR
+    if (info->resources->configTimestamp != screen->priv->info->resources->configTimestamp)
+	    changed = TRUE;
+#endif
+
+    screen_info_free (screen->priv->info);
+	
+    screen->priv->info = info;
+
+    if (changed || force_callback)
+	g_signal_emit (G_OBJECT (screen), screen_signals[SCREEN_CHANGED], 0);
+    
+    return changed;
+}
+
+static GdkFilterReturn
+screen_on_event (GdkXEvent *xevent,
+		 GdkEvent *event,
+		 gpointer data)
+{
+#ifdef HAVE_RANDR
+    XfceRRScreen *screen = data;
+    XfceRRScreenPrivate *priv = screen->priv;
+    XEvent *e = xevent;
+    int event_num;
+
+    if (!e)
+	return GDK_FILTER_CONTINUE;
+
+    event_num = e->type - priv->randr_event_base;
+
+    if (event_num == RRScreenChangeNotify) {
+	/* We don't reprobe the hardware; we just fetch the X server's latest
+	 * state.  The server already knows the new state of the outputs; that's
+	 * why it sent us an event!
+	 */
+        screen_update (screen, TRUE, FALSE, NULL); /* NULL-GError */
+#if 0
+	/* Enable this code to get a dialog showing the RANDR timestamps, for debugging purposes */
+	{
+	    GtkWidget *dialog;
+	    XRRScreenChangeNotifyEvent *rr_event;
+	    static int dialog_num;
+
+	    rr_event = (XRRScreenChangeNotifyEvent *) e;
+
+	    dialog = gtk_message_dialog_new (NULL,
+					     0,
+					     GTK_MESSAGE_INFO,
+					     GTK_BUTTONS_CLOSE,
+					     "RRScreenChangeNotify timestamps (%d):\n"
+					     "event change: %u\n"
+					     "event config: %u\n"
+					     "event serial: %lu\n"
+					     "----------------------"
+					     "screen change: %u\n"
+					     "screen config: %u\n",
+					     dialog_num++,
+					     (guint32) rr_event->timestamp,
+					     (guint32) rr_event->config_timestamp,
+					     rr_event->serial,
+					     (guint32) priv->info->resources->timestamp,
+					     (guint32) priv->info->resources->configTimestamp);
+	    g_signal_connect (dialog, "response",
+			      G_CALLBACK (gtk_widget_destroy), NULL);
+	    gtk_widget_show (dialog);
+	}
+#endif
+    }
+#if 0
+    /* WHY THIS CODE IS DISABLED:
+     *
+     * Note that in xfce_rr_screen_new(), we only select for
+     * RRScreenChangeNotifyMask.  We used to select for other values in
+     * RR*NotifyMask, but we weren't really doing anything useful with those
+     * events.  We only care about "the screens changed in some way or another"
+     * for now.
+     *
+     * If we ever run into a situtation that could benefit from processing more
+     * detailed events, we can enable this code again.
+     *
+     * Note that the X server sends RRScreenChangeNotify in conjunction with the
+     * more detailed events from RANDR 1.2 - see xserver/randr/randr.c:TellChanged().
+     */
+    else if (event_num == RRNotify)
+    {
+	/* Other RandR events */
+
+	XRRNotifyEvent *event = (XRRNotifyEvent *)e;
+
+	/* Here we can distinguish between RRNotify events supported
+	 * since RandR 1.2 such as RRNotify_OutputProperty.  For now, we
+	 * don't have anything special to do for particular subevent types, so
+	 * we leave this as an empty switch().
+	 */
+	switch (event->subtype)
+	{
+	default:
+	    break;
+	}
+
+	/* No need to reprobe hardware here */
+	screen_update (screen, TRUE, FALSE, NULL); /* NULL-GError */
+    }
+#endif
+
+#endif /* HAVE_RANDR */
+
+    /* Pass the event on to GTK+ */
+    return GDK_FILTER_CONTINUE;
+}
+
+static gboolean
+xfce_rr_screen_initable_init (GInitable *initable, GCancellable *canc, GError **error)
+
+{
+    XfceRRScreen *self = XFCE_RR_SCREEN (initable);
+    XfceRRScreenPrivate *priv = self->priv;
+    Display *dpy = GDK_SCREEN_XDISPLAY (self->priv->gdk_screen);
+    int event_base;
+    int ignore;
+
+    priv->connector_type_atom = XInternAtom (dpy, "ConnectorType", FALSE);
+
+#ifdef HAVE_RANDR
+    if (XRRQueryExtension (dpy, &event_base, &ignore))
+    {
+        priv->randr_event_base = event_base;
+
+        XRRQueryVersion (dpy, &priv->rr_major_version, &priv->rr_minor_version);
+        if (priv->rr_major_version < 1 || (priv->rr_major_version == 1 && priv->rr_minor_version < 3)) {
+            g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_NO_RANDR_EXTENSION,
+                "RANDR extension is too old (must be at least 1.3)");
+            return FALSE;
+        }
+
+        priv->info = screen_info_new (self, TRUE, error);
+
+        if (!priv->info) {
+	    return FALSE;
+	}
+
+        XRRSelectInput (priv->xdisplay,
+            priv->xroot,
+            RRScreenChangeNotifyMask);
+        gdk_x11_register_standard_event_type (gdk_screen_get_display (priv->gdk_screen),
+                          event_base,
+                          RRNotify + 1);
+        gdk_window_add_filter (priv->gdk_root, screen_on_event, self);
+
+        return TRUE;
+    }
+    else
+    {
+#endif /* HAVE_RANDR */
+    g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_NO_RANDR_EXTENSION,
+        _("RANDR extension is not present"));
+
+    return FALSE;
+
+#ifdef HAVE_RANDR
+   }
+#endif
+}
+
+void
+xfce_rr_screen_initable_iface_init (GInitableIface *iface)
+{
+    iface->init = xfce_rr_screen_initable_init;
+}
+
+void
+    xfce_rr_screen_finalize (GObject *gobject)
+{
+    XfceRRScreen *screen = XFCE_RR_SCREEN (gobject);
+
+    gdk_window_remove_filter (screen->priv->gdk_root, screen_on_event, screen);
+
+    if (screen->priv->info)
+      screen_info_free (screen->priv->info);
+
+    G_OBJECT_CLASS (xfce_rr_screen_parent_class)->finalize (gobject);
+}
+
+void
+xfce_rr_screen_set_property (GObject *gobject, guint property_id, const GValue *value, GParamSpec *property)
+{
+    XfceRRScreen *self = XFCE_RR_SCREEN (gobject);
+    XfceRRScreenPrivate *priv = self->priv;
+
+    switch (property_id)
+    {
+    case SCREEN_PROP_GDK_SCREEN:
+        priv->gdk_screen = g_value_get_object (value);
+        priv->gdk_root = gdk_screen_get_root_window (priv->gdk_screen);
+        priv->xroot = GDK_WINDOW_XID (priv->gdk_root);
+        priv->xdisplay = GDK_SCREEN_XDISPLAY (priv->gdk_screen);
+        priv->xscreen = gdk_x11_screen_get_xscreen (priv->gdk_screen);
+        return;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, property);
+        return;
+    }
+}
+
+void
+xfce_rr_screen_get_property (GObject *gobject, guint property_id, GValue *value, GParamSpec *property)
+{
+    XfceRRScreen *self = XFCE_RR_SCREEN (gobject);
+    XfceRRScreenPrivate *priv = self->priv;
+
+    switch (property_id)
+    {
+    case SCREEN_PROP_GDK_SCREEN:
+        g_value_set_object (value, priv->gdk_screen);
+        return;
+     default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, property);
+        return;
+    }
+}
+
+void
+xfce_rr_screen_class_init (XfceRRScreenClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+    g_type_class_add_private (klass, sizeof (XfceRRScreenPrivate));
+
+    gobject_class->set_property = xfce_rr_screen_set_property;
+    gobject_class->get_property = xfce_rr_screen_get_property;
+    gobject_class->finalize = xfce_rr_screen_finalize;
+
+    g_object_class_install_property(
+        gobject_class,
+        SCREEN_PROP_GDK_SCREEN,
+        g_param_spec_object (
+            "gdk-screen",
+            "GDK Screen",
+            "The GDK Screen represented by this XfceRRScreen",
+            GDK_TYPE_SCREEN,
+	    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)
+        );
+
+    screen_signals[SCREEN_CHANGED] = g_signal_new("changed",
+        G_TYPE_FROM_CLASS (gobject_class),
+        G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
+        G_STRUCT_OFFSET (XfceRRScreenClass, changed),
+        NULL,
+        NULL,
+        g_cclosure_marshal_VOID__VOID,
+        G_TYPE_NONE,
+        0);
+}
+
+void
+xfce_rr_screen_init (XfceRRScreen *self)
+{
+    XfceRRScreenPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, XFCE_TYPE_RR_SCREEN, XfceRRScreenPrivate);
+    self->priv = priv;
+
+    priv->gdk_screen = NULL;
+    priv->gdk_root = NULL;
+    priv->xdisplay = NULL;
+    priv->xroot = None;
+    priv->xscreen = NULL;
+    priv->info = NULL;
+    priv->rr_major_version = 0;
+    priv->rr_minor_version = 0;
+}
+
+/**
+ * xfce_rr_screen_new:
+ * @screen: the #GdkScreen on which to operate
+ * @error: will be set if XRandR is not supported
+ *
+ * Creates a new #XfceRRScreen instance
+ *
+ * Returns: a new #XfceRRScreen instance or NULL if screen could not be created,
+ * for instance if the driver does not support Xrandr 1.2
+ */
+XfceRRScreen *
+xfce_rr_screen_new (GdkScreen *screen,
+                    GError **error)
+{
+    //bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
+    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+    return g_initable_new (XFCE_TYPE_RR_SCREEN, NULL, error, "gdk-screen", screen, NULL);
+}
+
+void
+xfce_rr_screen_set_size (XfceRRScreen *screen,
+			  int	      width,
+			  int       height,
+			  int       mm_width,
+			  int       mm_height)
+{
+    g_return_if_fail (XFCE_IS_RR_SCREEN (screen));
+
+#ifdef HAVE_RANDR
+	GdkDisplay *display;
+
+	display = gdk_display_get_default ();
+    gdk_x11_display_error_trap_push (display);
+    XRRSetScreenSize (screen->priv->xdisplay, screen->priv->xroot,
+		      width, height, mm_width, mm_height);
+    gdk_x11_display_error_trap_pop_ignored (display);
+#endif
+}
+
+/**
+ * xfce_rr_screen_get_ranges:
+ * @screen: a #XfceRRScreen
+ * @min_width: (out): the minimum width
+ * @max_width: (out): the maximum width
+ * @min_height: (out): the minimum height
+ * @max_height: (out): the maximum height
+ *
+ * Get the ranges of the screen
+ */
+void
+xfce_rr_screen_get_ranges (XfceRRScreen *screen,
+			    int	          *min_width,
+			    int	          *max_width,
+			    int           *min_height,
+			    int	          *max_height)
+{
+    XfceRRScreenPrivate *priv;
+
+    g_return_if_fail (XFCE_IS_RR_SCREEN (screen));
+
+    priv = screen->priv;
+    
+    if (min_width)
+	*min_width = priv->info->min_width;
+    
+    if (max_width)
+	*max_width = priv->info->max_width;
+    
+    if (min_height)
+	*min_height = priv->info->min_height;
+    
+    if (max_height)
+	*max_height = priv->info->max_height;
+}
+
+/**
+ * xfce_rr_screen_get_timestamps:
+ * @screen: a #XfceRRScreen
+ * @change_timestamp_ret: (out): Location in which to store the timestamp at which the RANDR configuration was last changed
+ * @config_timestamp_ret: (out): Location in which to store the timestamp at which the RANDR configuration was last obtained
+ *
+ * Queries the two timestamps that the X RANDR extension maintains.  The X
+ * server will prevent change requests for stale configurations, those whose
+ * timestamp is not equal to that of the latest request for configuration.  The
+ * X server will also prevent change requests that have an older timestamp to
+ * the latest change request.
+ */
+void
+xfce_rr_screen_get_timestamps (XfceRRScreen *screen,
+				guint32       *change_timestamp_ret,
+				guint32       *config_timestamp_ret)
+{
+    XfceRRScreenPrivate *priv;
+
+    g_return_if_fail (XFCE_IS_RR_SCREEN (screen));
+
+    priv = screen->priv;
+
+#ifdef HAVE_RANDR
+    if (change_timestamp_ret)
+	*change_timestamp_ret = priv->info->resources->timestamp;
+
+    if (config_timestamp_ret)
+	*config_timestamp_ret = priv->info->resources->configTimestamp;
+#endif
+}
+
+static gboolean
+force_timestamp_update (XfceRRScreen *screen)
+{
+#ifdef HAVE_RANDR
+    XfceRRScreenPrivate *priv = screen->priv;
+    XfceRRCrtc *crtc;
+    XRRCrtcInfo *current_info;
+	GdkDisplay *display;
+    Status status;
+    gboolean timestamp_updated;
+
+    timestamp_updated = FALSE;
+
+    crtc = priv->info->crtcs[0];
+
+    if (crtc == NULL)
+	goto out;
+
+    current_info = XRRGetCrtcInfo (priv->xdisplay,
+				   priv->info->resources,
+				   crtc->id);
+
+    if (current_info == NULL)
+	  goto out;
+
+	display = gdk_display_get_default ();
+    gdk_x11_display_error_trap_push (display);
+    status = XRRSetCrtcConfig (priv->xdisplay,
+			       priv->info->resources,
+			       crtc->id,
+			       current_info->timestamp,
+			       current_info->x,
+			       current_info->y,
+			       current_info->mode,
+			       current_info->rotation,
+			       current_info->outputs,
+			       current_info->noutput);
+
+    XRRFreeCrtcInfo (current_info);
+
+    gdk_display_flush (display);
+    if (gdk_x11_display_error_trap_pop (display))
+	goto out;
+
+    if (status == RRSetConfigSuccess)
+	timestamp_updated = TRUE;
+out:
+    return timestamp_updated;
+#else
+    return FALSE;
+#endif
+}
+
+/**
+ * xfce_rr_screen_refresh:
+ * @screen: a #XfceRRScreen
+ * @error: location to store error, or %NULL
+ *
+ * Refreshes the screen configuration, and calls the screen's callback if it
+ * exists and if the screen's configuration changed.
+ *
+ * Return value: TRUE if the screen's configuration changed; otherwise, the
+ * function returns FALSE and a NULL error if the configuration didn't change,
+ * or FALSE and a non-NULL error if there was an error while refreshing the
+ * configuration.
+ */
+gboolean
+xfce_rr_screen_refresh (XfceRRScreen *screen,
+			 GError       **error)
+{
+    gboolean refreshed;
+
+    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+    gdk_x11_display_grab (gdk_screen_get_display (screen->priv->gdk_screen));
+
+    refreshed = screen_update (screen, FALSE, TRUE, error);
+    force_timestamp_update (screen); /* this is to keep other clients from thinking that the X server re-detected things by itself - bgo#621046 */
+
+    gdk_x11_display_ungrab (gdk_screen_get_display (screen->priv->gdk_screen));
+
+    return refreshed;
+}
+
+/**
+ * xfce_rr_screen_list_modes:
+ *
+ * List available XRandR modes
+ *
+ * Returns: (array zero-terminated=1) (transfer none):
+ */
+XfceRRMode **
+xfce_rr_screen_list_modes (XfceRRScreen *screen)
+{
+    g_return_val_if_fail (XFCE_IS_RR_SCREEN (screen), NULL);
+    g_return_val_if_fail (screen->priv->info != NULL, NULL);
+    
+    return screen->priv->info->modes;
+}
+
+/**
+ * xfce_rr_screen_list_clone_modes:
+ *
+ * List available XRandR clone modes
+ *
+ * Returns: (array zero-terminated=1) (transfer none):
+ */
+XfceRRMode **
+xfce_rr_screen_list_clone_modes   (XfceRRScreen *screen)
+{
+    g_return_val_if_fail (XFCE_IS_RR_SCREEN (screen), NULL);
+    g_return_val_if_fail (screen->priv->info != NULL, NULL);
+
+    return screen->priv->info->clone_modes;
+}
+
+/**
+ * xfce_rr_screen_list_crtcs:
+ *
+ * List all CRTCs
+ *
+ * Returns: (array zero-terminated=1) (transfer none):
+ */
+XfceRRCrtc **
+xfce_rr_screen_list_crtcs (XfceRRScreen *screen)
+{
+    g_return_val_if_fail (XFCE_IS_RR_SCREEN (screen), NULL);
+    g_return_val_if_fail (screen->priv->info != NULL, NULL);
+    
+    return screen->priv->info->crtcs;
+}
+
+/**
+ * xfce_rr_screen_list_outputs:
+ *
+ * List all outputs
+ *
+ * Returns: (array zero-terminated=1) (transfer none):
+ */
+XfceRROutput **
+xfce_rr_screen_list_outputs (XfceRRScreen *screen)
+{
+    g_return_val_if_fail (XFCE_IS_RR_SCREEN (screen), NULL);
+    g_return_val_if_fail (screen->priv->info != NULL, NULL);
+    
+    return screen->priv->info->outputs;
+}
+
+/**
+ * xfce_rr_screen_get_crtc_by_id:
+ *
+ * Returns: (transfer none): the CRTC identified by @id
+ */
+XfceRRCrtc *
+xfce_rr_screen_get_crtc_by_id (XfceRRScreen *screen,
+				guint32        id)
+{
+    XfceRRCrtc **crtcs;
+    int i;
+    
+    g_return_val_if_fail (XFCE_IS_RR_SCREEN (screen), NULL);
+    g_return_val_if_fail (screen->priv->info != NULL, NULL);
+
+    crtcs = screen->priv->info->crtcs;
+    
+    for (i = 0; crtcs[i] != NULL; ++i)
+    {
+	if (crtcs[i]->id == id)
+	    return crtcs[i];
+    }
+    
+    return NULL;
+}
+
+/**
+ * xfce_rr_screen_get_output_by_id:
+ *
+ * Returns: (transfer none): the output identified by @id
+ */
+XfceRROutput *
+xfce_rr_screen_get_output_by_id (XfceRRScreen *screen,
+				  guint32        id)
+{
+    XfceRROutput **outputs;
+    int i;
+    
+    g_return_val_if_fail (XFCE_IS_RR_SCREEN (screen), NULL);
+    g_return_val_if_fail (screen->priv->info != NULL, NULL);
+
+    outputs = screen->priv->info->outputs;
+
+    for (i = 0; outputs[i] != NULL; ++i)
+    {
+	if (outputs[i]->id == id)
+	    return outputs[i];
+    }
+    
+    return NULL;
+}
+
+/* XfceRROutput */
+static XfceRROutput *
+output_new (ScreenInfo *info, RROutput id)
+{
+    XfceRROutput *output = g_slice_new0 (XfceRROutput);
+    
+    output->id = id;
+    output->info = info;
+    
+    return output;
+}
+
+static guint8 *
+get_property (Display *dpy,
+	      RROutput output,
+	      Atom atom,
+	      int *len)
+{
+#ifdef HAVE_RANDR
+    unsigned char *prop;
+    int actual_format;
+    unsigned long nitems, bytes_after;
+    Atom actual_type;
+    guint8 *result;
+    
+    XRRGetOutputProperty (dpy, output, atom,
+			  0, 100, False, False,
+			  AnyPropertyType,
+			  &actual_type, &actual_format,
+			  &nitems, &bytes_after, &prop);
+    
+    if (actual_type == XA_INTEGER && actual_format == 8)
+    {
+	result = g_memdup (prop, nitems);
+	if (len)
+	    *len = nitems;
+    }
+    else
+    {
+	result = NULL;
+    }
+    
+    XFree (prop);
+    
+    return result;
+#else
+    return NULL;
+#endif /* HAVE_RANDR */
+}
+
+static guint8 *
+read_edid_data (XfceRROutput *output, int *len)
+{
+    Atom edid_atom;
+    guint8 *result;
+
+    edid_atom = XInternAtom (DISPLAY (output), "EDID", FALSE);
+    result = get_property (DISPLAY (output),
+			   output->id, edid_atom, len);
+
+    if (!result)
+    {
+	edid_atom = XInternAtom (DISPLAY (output), "EDID_DATA", FALSE);
+	result = get_property (DISPLAY (output),
+			       output->id, edid_atom, len);
+    }
+
+    if (result)
+    {
+	if (*len % 128 == 0)
+	    return result;
+	else
+	    g_free (result);
+    }
+    
+    return NULL;
+}
+
+static char *
+get_connector_type_string (XfceRROutput *output)
+{
+#ifdef HAVE_RANDR
+    char *result;
+    unsigned char *prop;
+    int actual_format;
+    unsigned long nitems, bytes_after;
+    Atom actual_type;
+    Atom connector_type;
+    char *connector_type_str;
+
+    result = NULL;
+
+    if (XRRGetOutputProperty (DISPLAY (output), output->id, output->info->screen->priv->connector_type_atom,
+			      0, 100, False, False,
+			      AnyPropertyType,
+			      &actual_type, &actual_format,
+			      &nitems, &bytes_after, &prop) != Success)
+	return NULL;
+
+    if (!(actual_type == XA_ATOM && actual_format == 32 && nitems == 1))
+	goto out;
+
+    connector_type = *((Atom *) prop);
+
+    connector_type_str = XGetAtomName (DISPLAY (output), connector_type);
+    if (connector_type_str) {
+	result = g_strdup (connector_type_str); /* so the caller can g_free() it */
+	XFree (connector_type_str);
+    }
+
+out:
+
+    XFree (prop);
+
+    return result;
+#else
+    return NULL;
+#endif
+}
+
+#ifdef HAVE_RANDR
+static gboolean
+output_initialize (XfceRROutput *output, XRRScreenResources *res, GError **error)
+{
+    XRROutputInfo *info = XRRGetOutputInfo (
+	DISPLAY (output), res, output->id);
+    GPtrArray *a;
+    int i;
+    
+#if 0
+    g_print ("Output %lx Timestamp: %u\n", output->id, (guint32)info->timestamp);
+#endif
+    
+    if (!info || !output->info)
+    {
+	/* FIXME: see the comment in crtc_initialize() */
+	/* Translators: here, an "output" is a video output */
+	g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_RANDR_ERROR,
+		     _("could not get information about output %d"),
+		     (int) output->id);
+	return FALSE;
+    }
+    
+    output->name = g_strdup (info->name); /* FIXME: what is nameLen used for? */
+    output->current_crtc = crtc_by_id (output->info, info->crtc);
+    output->width_mm = info->mm_width;
+    output->height_mm = info->mm_height;
+    output->connected = (info->connection == RR_Connected);
+    output->connector_type = get_connector_type_string (output);
+
+    /* Possible crtcs */
+    a = g_ptr_array_new ();
+    
+    for (i = 0; i < info->ncrtc; ++i)
+    {
+	XfceRRCrtc *crtc = crtc_by_id (output->info, info->crtcs[i]);
+	
+	if (crtc)
+	    g_ptr_array_add (a, crtc);
+    }
+    g_ptr_array_add (a, NULL);
+    output->possible_crtcs = (XfceRRCrtc **)g_ptr_array_free (a, FALSE);
+    
+    /* Clones */
+    a = g_ptr_array_new ();
+    for (i = 0; i < info->nclone; ++i)
+    {
+	XfceRROutput *xfce_rr_output = xfce_rr_output_by_id (output->info, info->clones[i]);
+	
+	if (xfce_rr_output)
+	    g_ptr_array_add (a, xfce_rr_output);
+    }
+    g_ptr_array_add (a, NULL);
+    output->clones = (XfceRROutput **)g_ptr_array_free (a, FALSE);
+    
+    /* Modes */
+    a = g_ptr_array_new ();
+    for (i = 0; i < info->nmode; ++i)
+    {
+	XfceRRMode *mode = mode_by_id (output->info, info->modes[i]);
+	
+	if (mode)
+	    g_ptr_array_add (a, mode);
+    }
+    g_ptr_array_add (a, NULL);
+    output->modes = (XfceRRMode **)g_ptr_array_free (a, FALSE);
+    
+    output->n_preferred = info->npreferred;
+    
+    /* Edid data */
+    output->edid_data = read_edid_data (output, &output->edid_size);
+    
+    XRRFreeOutputInfo (info);
+
+    return TRUE;
+}
+#endif /* HAVE_RANDR */
+
+static XfceRROutput*
+output_copy (const XfceRROutput *from)
+{
+    GPtrArray *array;
+    XfceRRCrtc **p_crtc;
+    XfceRROutput **p_output;
+    XfceRRMode **p_mode;
+    XfceRROutput *output = g_slice_new0 (XfceRROutput);
+
+    output->id = from->id;
+    output->info = from->info;
+    output->name = g_strdup (from->name);
+    output->current_crtc = from->current_crtc;
+    output->width_mm = from->width_mm;
+    output->height_mm = from->height_mm;
+    output->connected = from->connected;
+    output->n_preferred = from->n_preferred;
+    output->connector_type = g_strdup (from->connector_type);
+
+    array = g_ptr_array_new ();
+    for (p_crtc = from->possible_crtcs; *p_crtc != NULL; p_crtc++)
+    {
+        g_ptr_array_add (array, *p_crtc);
+    }
+    output->possible_crtcs = (XfceRRCrtc**) g_ptr_array_free (array, FALSE);
+
+    array = g_ptr_array_new ();
+    for (p_output = from->clones; *p_output != NULL; p_output++)
+    {
+        g_ptr_array_add (array, *p_output);
+    }
+    output->clones = (XfceRROutput**) g_ptr_array_free (array, FALSE);
+
+    array = g_ptr_array_new ();
+    for (p_mode = from->modes; *p_mode != NULL; p_mode++)
+    {
+        g_ptr_array_add (array, *p_mode);
+    }
+    output->modes = (XfceRRMode**) g_ptr_array_free (array, FALSE);
+
+    output->edid_size = from->edid_size;
+    output->edid_data = g_memdup (from->edid_data, from->edid_size);
+
+    return output;
+}
+
+static void
+output_free (XfceRROutput *output)
+{
+    g_free (output->clones);
+    g_free (output->modes);
+    g_free (output->possible_crtcs);
+    g_free (output->edid_data);
+    g_free (output->name);
+    g_free (output->connector_type);
+    g_slice_free (XfceRROutput, output);
+}
+
+guint32
+xfce_rr_output_get_id (XfceRROutput *output)
+{
+    g_assert(output != NULL);
+    
+    return output->id;
+}
+
+const guint8 *
+xfce_rr_output_get_edid_data (XfceRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, NULL);
+    
+    return output->edid_data;
+}
+
+/**
+ * xfce_rr_screen_get_output_by_name:
+ *
+ * Returns: (transfer none): the output identified by @name
+ */
+XfceRROutput *
+xfce_rr_screen_get_output_by_name (XfceRRScreen *screen,
+				    const char    *name)
+{
+    int i;
+    
+    g_return_val_if_fail (XFCE_IS_RR_SCREEN (screen), NULL);
+    g_return_val_if_fail (screen->priv->info != NULL, NULL);
+    
+    for (i = 0; screen->priv->info->outputs[i] != NULL; ++i)
+    {
+	XfceRROutput *output = screen->priv->info->outputs[i];
+	
+	if (strcmp (output->name, name) == 0)
+	    return output;
+    }
+    
+    return NULL;
+}
+
+/**
+ * xfce_rr_output_get_crtc:
+ * @output: a #XfceRROutput
+ * Returns: (transfer none):
+ */
+XfceRRCrtc *
+xfce_rr_output_get_crtc (XfceRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, NULL);
+    
+    return output->current_crtc;
+}
+
+/**
+ * xfce_rr_output_get_possible_crtcs:
+ * @output: a #XfceRROutput
+ * Returns: (array zero-terminated=1) (transfer none):
+ */
+XfceRRCrtc **
+xfce_rr_output_get_possible_crtcs (XfceRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, NULL);
+
+    return output->possible_crtcs;
+}
+
+/* Returns NULL if the ConnectorType property is not available */
+const char *
+xfce_rr_output_get_connector_type (XfceRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, NULL);
+
+    return output->connector_type;
+}
+
+gboolean
+_xfce_rr_output_name_is_laptop (const char *name)
+{
+    if (!name)
+        return FALSE;
+
+    if (strstr (name, "lvds") || /* Most drivers use an "LVDS" prefix... */
+        strstr (name, "LVDS") ||
+        strstr (name, "Lvds") ||
+        strstr (name, "LCD")  || /* ... but fglrx uses "LCD" in some versions.  Shoot me now, kthxbye. */
+        strstr (name, "eDP"))    /* eDP is for internal laptop panel connections */
+        return TRUE;
+
+    return FALSE;
+}
+
+gboolean
+xfce_rr_output_is_laptop (XfceRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, FALSE);
+
+    if (!output->connected)
+        return FALSE;
+
+    if (g_strcmp0 (output->connector_type, XFCE_RR_CONNECTOR_TYPE_PANEL) == 0)
+        return TRUE;
+
+    /* Fallback (see https://bugs.freedesktop.org/show_bug.cgi?id=26736) */
+    return _xfce_rr_output_name_is_laptop (output->name);
+}
+
+/**
+ * xfce_rr_output_get_current_mode:
+ * @output: a #XfceRROutput
+ * Returns: (transfer none): the current mode of this output
+ */
+XfceRRMode *
+xfce_rr_output_get_current_mode (XfceRROutput *output)
+{
+    XfceRRCrtc *crtc;
+    
+    g_return_val_if_fail (output != NULL, NULL);
+    
+    if ((crtc = xfce_rr_output_get_crtc (output)))
+	return xfce_rr_crtc_get_current_mode (crtc);
+    
+    return NULL;
+}
+
+/**
+ * xfce_rr_output_get_position:
+ * @output: a #XfceRROutput
+ * @x: (out) (allow-none):
+ * @y: (out) (allow-none):
+ */
+void
+xfce_rr_output_get_position (XfceRROutput   *output,
+			      int             *x,
+			      int             *y)
+{
+    XfceRRCrtc *crtc;
+    
+    g_return_if_fail (output != NULL);
+    
+    if ((crtc = xfce_rr_output_get_crtc (output)))
+	xfce_rr_crtc_get_position (crtc, x, y);
+}
+
+const char *
+xfce_rr_output_get_name (XfceRROutput *output)
+{
+    g_assert (output != NULL);
+    return output->name;
+}
+
+int
+xfce_rr_output_get_width_mm (XfceRROutput *output)
+{
+    g_assert (output != NULL);
+    return output->width_mm;
+}
+
+int
+xfce_rr_output_get_height_mm (XfceRROutput *output)
+{
+    g_assert (output != NULL);
+    return output->height_mm;
+}
+
+/**
+ * xfce_rr_output_get_preferred_mode:
+ * @output: a #XfceRROutput
+ * Returns: (transfer none):
+ */
+XfceRRMode *
+xfce_rr_output_get_preferred_mode (XfceRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, NULL);
+    if (output->n_preferred)
+	return output->modes[0];
+    
+    return NULL;
+}
+
+/**
+ * xfce_rr_output_list_modes:
+ * @output: a #XfceRROutput
+ * Returns: (array zero-terminated=1) (transfer none):
+ */
+
+XfceRRMode **
+xfce_rr_output_list_modes (XfceRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, NULL);
+    return output->modes;
+}
+
+gboolean
+xfce_rr_output_is_connected (XfceRROutput *output)
+{
+    g_return_val_if_fail (output != NULL, FALSE);
+    return output->connected;
+}
+
+gboolean
+xfce_rr_output_supports_mode (XfceRROutput *output,
+			       XfceRRMode   *mode)
+{
+    int i;
+    
+    g_return_val_if_fail (output != NULL, FALSE);
+    g_return_val_if_fail (mode != NULL, FALSE);
+    
+    for (i = 0; output->modes[i] != NULL; ++i)
+    {
+	if (output->modes[i] == mode)
+	    return TRUE;
+    }
+    
+    return FALSE;
+}
+
+gboolean
+xfce_rr_output_can_clone (XfceRROutput *output,
+			   XfceRROutput *clone)
+{
+    int i;
+    
+    g_return_val_if_fail (output != NULL, FALSE);
+    g_return_val_if_fail (clone != NULL, FALSE);
+    
+    for (i = 0; output->clones[i] != NULL; ++i)
+    {
+	if (output->clones[i] == clone)
+	    return TRUE;
+    }
+    
+    return FALSE;
+}
+
+gboolean
+xfce_rr_output_get_is_primary (XfceRROutput *output)
+{
+#ifdef HAVE_RANDR
+    return output->info->primary == output->id;
+#else
+    return FALSE;
+#endif
+}
+
+void
+xfce_rr_screen_set_primary_output (XfceRRScreen *screen,
+                                    XfceRROutput *output)
+{
+#ifdef HAVE_RANDR
+    XfceRRScreenPrivate *priv;
+
+    g_return_if_fail (XFCE_IS_RR_SCREEN (screen));
+
+    priv = screen->priv;
+
+    RROutput id;
+
+    if (output)
+        id = output->id;
+    else
+        id = None;
+
+    XRRSetOutputPrimary (priv->xdisplay, priv->xroot, id);
+#endif
+}
+
+/* XfceRRCrtc */
+typedef struct
+{
+    Rotation xrot;
+    XfceRRRotation rot;
+} RotationMap;
+
+static const RotationMap rotation_map[] =
+{
+    { RR_Rotate_0, XFCE_RR_ROTATION_0 },
+    { RR_Rotate_90, XFCE_RR_ROTATION_90 },
+    { RR_Rotate_180, XFCE_RR_ROTATION_180 },
+    { RR_Rotate_270, XFCE_RR_ROTATION_270 },
+    { RR_Reflect_X, XFCE_RR_REFLECT_X },
+    { RR_Reflect_Y, XFCE_RR_REFLECT_Y },
+};
+
+static XfceRRRotation
+xfce_rr_rotation_from_xrotation (Rotation r)
+{
+    int i;
+    XfceRRRotation result = 0;
+    
+    for (i = 0; i < G_N_ELEMENTS (rotation_map); ++i)
+    {
+	if (r & rotation_map[i].xrot)
+	    result |= rotation_map[i].rot;
+    }
+    
+    return result;
+}
+
+static Rotation
+xrotation_from_rotation (XfceRRRotation r)
+{
+    int i;
+    Rotation result = 0;
+    
+    for (i = 0; i < G_N_ELEMENTS (rotation_map); ++i)
+    {
+	if (r & rotation_map[i].rot)
+	    result |= rotation_map[i].xrot;
+    }
+    
+    return result;
+}
+
+#ifndef MATE_DISABLE_DEPRECATED_SOURCE
+gboolean
+xfce_rr_crtc_set_config (XfceRRCrtc      *crtc,
+			  int               x,
+			  int               y,
+			  XfceRRMode      *mode,
+			  XfceRRRotation   rotation,
+			  XfceRROutput   **outputs,
+			  int               n_outputs,
+			  GError          **error)
+{
+    return xfce_rr_crtc_set_config_with_time (crtc, GDK_CURRENT_TIME, x, y, mode, rotation, outputs, n_outputs, error);
+}
+#endif
+
+gboolean
+xfce_rr_crtc_set_config_with_time (XfceRRCrtc      *crtc,
+				    guint32           timestamp,
+				    int               x,
+				    int               y,
+				    XfceRRMode      *mode,
+				    XfceRRRotation   rotation,
+				    XfceRROutput   **outputs,
+				    int               n_outputs,
+				    GError          **error)
+{
+#ifdef HAVE_RANDR
+    ScreenInfo *info;
+    GArray *output_ids;
+	GdkDisplay *display;
+    Status status;
+    gboolean result;
+    int i;
+    
+    g_return_val_if_fail (crtc != NULL, FALSE);
+    g_return_val_if_fail (mode != NULL || outputs == NULL || n_outputs == 0, FALSE);
+    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+    
+    info = crtc->info;
+    
+    if (mode)
+    {
+	if (x + mode->width > info->max_width
+	    || y + mode->height > info->max_height)
+	{
+	    g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_BOUNDS_ERROR,
+			 /* Translators: the "position", "size", and "maximum"
+			  * words here are not keywords; please translate them
+			  * as usual.  A CRTC is a CRT Controller (this is X terminology) */
+			 _("requested position/size for CRTC %d is outside the allowed limit: "
+			   "position=(%d, %d), size=(%d, %d), maximum=(%d, %d)"),
+			 (int) crtc->id,
+			 x, y,
+			 mode->width, mode->height,
+			 info->max_width, info->max_height);
+	    return FALSE;
+	}
+    }
+    
+    output_ids = g_array_new (FALSE, FALSE, sizeof (RROutput));
+    
+    if (outputs)
+    {
+	for (i = 0; i < n_outputs; ++i)
+	    g_array_append_val (output_ids, outputs[i]->id);
+    }
+
+	display = gdk_display_get_default ();
+    gdk_x11_display_error_trap_push (display);
+    status = XRRSetCrtcConfig (DISPLAY (crtc), info->resources, crtc->id,
+			       timestamp, 
+			       x, y,
+			       mode ? mode->id : None,
+			       xrotation_from_rotation (rotation),
+			       (RROutput *)output_ids->data,
+			       output_ids->len);
+    
+    g_array_free (output_ids, TRUE);
+
+    if (gdk_x11_display_error_trap_pop (display) || status != RRSetConfigSuccess) {
+        /* Translators: CRTC is a CRT Controller (this is X terminology).
+         * It is *very* unlikely that you'll ever get this error, so it is
+         * only listed for completeness. */
+        g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_RANDR_ERROR,
+                     _("could not set the configuration for CRTC %d"),
+                     (int) crtc->id);
+        return FALSE;
+    } else {
+        result = TRUE;
+    }
+    
+    return result;
+#else
+    return FALSE;
+#endif /* HAVE_RANDR */
+}
+
+/**
+ * xfce_rr_crtc_get_current_mode:
+ * @crtc: a #XfceRRCrtc
+ * Returns: (transfer none): the current mode of this crtc
+ */
+XfceRRMode *
+xfce_rr_crtc_get_current_mode (XfceRRCrtc *crtc)
+{
+    g_return_val_if_fail (crtc != NULL, NULL);
+    
+    return crtc->current_mode;
+}
+
+guint32
+xfce_rr_crtc_get_id (XfceRRCrtc *crtc)
+{
+    g_return_val_if_fail (crtc != NULL, 0);
+    
+    return crtc->id;
+}
+
+gboolean
+xfce_rr_crtc_can_drive_output (XfceRRCrtc   *crtc,
+				XfceRROutput *output)
+{
+    int i;
+    
+    g_return_val_if_fail (crtc != NULL, FALSE);
+    g_return_val_if_fail (output != NULL, FALSE);
+    
+    for (i = 0; crtc->possible_outputs[i] != NULL; ++i)
+    {
+	if (crtc->possible_outputs[i] == output)
+	    return TRUE;
+    }
+    
+    return FALSE;
+}
+
+/* FIXME: merge with get_mode()? */
+
+/**
+ * xfce_rr_crtc_get_position:
+ * @crtc: a #XfceRRCrtc
+ * @x: (out) (allow-none):
+ * @y: (out) (allow-none):
+ */
+void
+xfce_rr_crtc_get_position (XfceRRCrtc *crtc,
+			    int         *x,
+			    int         *y)
+{
+    g_return_if_fail (crtc != NULL);
+    
+    if (x)
+	*x = crtc->x;
+    
+    if (y)
+	*y = crtc->y;
+}
+
+/* FIXME: merge with get_mode()? */
+XfceRRRotation
+xfce_rr_crtc_get_current_rotation (XfceRRCrtc *crtc)
+{
+    g_assert(crtc != NULL);
+    return crtc->current_rotation;
+}
+
+XfceRRRotation
+xfce_rr_crtc_get_rotations (XfceRRCrtc *crtc)
+{
+    g_assert(crtc != NULL);
+    return crtc->rotations;
+}
+
+gboolean
+xfce_rr_crtc_supports_rotation (XfceRRCrtc *   crtc,
+				 XfceRRRotation rotation)
+{
+    g_return_val_if_fail (crtc != NULL, FALSE);
+    return (crtc->rotations & rotation);
+}
+
+static XfceRRCrtc *
+crtc_new (ScreenInfo *info, RROutput id)
+{
+    XfceRRCrtc *crtc = g_slice_new0 (XfceRRCrtc);
+    
+    crtc->id = id;
+    crtc->info = info;
+    
+    return crtc;
+}
+
+static XfceRRCrtc *
+crtc_copy (const XfceRRCrtc *from)
+{
+    XfceRROutput **p_output;
+    GPtrArray *array;
+    XfceRRCrtc *to = g_slice_new0 (XfceRRCrtc);
+
+    to->info = from->info;
+    to->id = from->id;
+    to->current_mode = from->current_mode;
+    to->x = from->x;
+    to->y = from->y;
+    to->current_rotation = from->current_rotation;
+    to->rotations = from->rotations;
+    to->gamma_size = from->gamma_size;
+
+    array = g_ptr_array_new ();
+    for (p_output = from->current_outputs; *p_output != NULL; p_output++)
+    {
+        g_ptr_array_add (array, *p_output);
+    }
+    to->current_outputs = (XfceRROutput**) g_ptr_array_free (array, FALSE);
+
+    array = g_ptr_array_new ();
+    for (p_output = from->possible_outputs; *p_output != NULL; p_output++)
+    {
+        g_ptr_array_add (array, *p_output);
+    }
+    to->possible_outputs = (XfceRROutput**) g_ptr_array_free (array, FALSE);
+
+    return to;
+}
+
+#ifdef HAVE_RANDR
+static gboolean
+crtc_initialize (XfceRRCrtc        *crtc,
+		 XRRScreenResources *res,
+		 GError            **error)
+{
+    XRRCrtcInfo *info = XRRGetCrtcInfo (DISPLAY (crtc), res, crtc->id);
+    GPtrArray *a;
+    int i;
+    
+#if 0
+    g_print ("CRTC %lx Timestamp: %u\n", crtc->id, (guint32)info->timestamp);
+#endif
+    
+    if (!info)
+    {
+	/* FIXME: We need to reaquire the screen resources */
+	/* FIXME: can we actually catch BadRRCrtc, and does it make sense to emit that? */
+
+	/* Translators: CRTC is a CRT Controller (this is X terminology).
+	 * It is *very* unlikely that you'll ever get this error, so it is
+	 * only listed for completeness. */
+	g_set_error (error, XFCE_RR_ERROR, XFCE_RR_ERROR_RANDR_ERROR,
+		     _("could not get information about CRTC %d"),
+		     (int) crtc->id);
+	return FALSE;
+    }
+    
+    /* XfceRRMode */
+    crtc->current_mode = mode_by_id (crtc->info, info->mode);
+    
+    crtc->x = info->x;
+    crtc->y = info->y;
+    
+    /* Current outputs */
+    a = g_ptr_array_new ();
+    for (i = 0; i < info->noutput; ++i)
+    {
+	XfceRROutput *output = xfce_rr_output_by_id (crtc->info, info->outputs[i]);
+	
+	if (output)
+	    g_ptr_array_add (a, output);
+    }
+    g_ptr_array_add (a, NULL);
+    crtc->current_outputs = (XfceRROutput **)g_ptr_array_free (a, FALSE);
+    
+    /* Possible outputs */
+    a = g_ptr_array_new ();
+    for (i = 0; i < info->npossible; ++i)
+    {
+	XfceRROutput *output = xfce_rr_output_by_id (crtc->info, info->possible[i]);
+	
+	if (output)
+	    g_ptr_array_add (a, output);
+    }
+    g_ptr_array_add (a, NULL);
+    crtc->possible_outputs = (XfceRROutput **)g_ptr_array_free (a, FALSE);
+    
+    /* Rotations */
+    crtc->current_rotation = xfce_rr_rotation_from_xrotation (info->rotation);
+    crtc->rotations = xfce_rr_rotation_from_xrotation (info->rotations);
+    
+    XRRFreeCrtcInfo (info);
+
+    /* get an store gamma size */
+    crtc->gamma_size = XRRGetCrtcGammaSize (DISPLAY (crtc), crtc->id);
+
+    return TRUE;
+}
+#endif
+
+static void
+crtc_free (XfceRRCrtc *crtc)
+{
+    g_free (crtc->current_outputs);
+    g_free (crtc->possible_outputs);
+    g_slice_free (XfceRRCrtc, crtc);
+}
+
+/* XfceRRMode */
+static XfceRRMode *
+mode_new (ScreenInfo *info, RRMode id)
+{
+    XfceRRMode *mode = g_slice_new0 (XfceRRMode);
+    
+    mode->id = id;
+    mode->info = info;
+    
+    return mode;
+}
+
+guint32
+xfce_rr_mode_get_id (XfceRRMode *mode)
+{
+    g_return_val_if_fail (mode != NULL, 0);
+    return mode->id;
+}
+
+guint
+xfce_rr_mode_get_width (XfceRRMode *mode)
+{
+    g_return_val_if_fail (mode != NULL, 0);
+    return mode->width;
+}
+
+int
+xfce_rr_mode_get_freq (XfceRRMode *mode)
+{
+    g_return_val_if_fail (mode != NULL, 0);
+    return (mode->freq) / 1000;
+}
+
+guint
+xfce_rr_mode_get_height (XfceRRMode *mode)
+{
+    g_return_val_if_fail (mode != NULL, 0);
+    return mode->height;
+}
+
+#ifdef HAVE_RANDR
+static void
+mode_initialize (XfceRRMode *mode, XRRModeInfo *info)
+{
+    g_assert (mode != NULL);
+    g_assert (info != NULL);
+    
+    mode->name = g_strdup (info->name);
+    mode->width = info->width;
+    mode->height = info->height;
+    mode->freq = ((info->dotClock / (double)info->hTotal) / info->vTotal + 0.5) * 1000;
+}
+#endif /* HAVE_RANDR */
+
+static XfceRRMode *
+mode_copy (const XfceRRMode *from)
+{
+    XfceRRMode *to = g_slice_new0 (XfceRRMode);
+
+    to->id = from->id;
+    to->info = from->info;
+    to->name = g_strdup (from->name);
+    to->width = from->width;
+    to->height = from->height;
+    to->freq = from->freq;
+
+    return to;
+}
+
+static void
+mode_free (XfceRRMode *mode)
+{
+    g_free (mode->name);
+    g_slice_free (XfceRRMode, mode);
+}
+
+void
+xfce_rr_crtc_set_gamma (XfceRRCrtc *crtc, int size,
+			 unsigned short *red,
+			 unsigned short *green,
+			 unsigned short *blue)
+{
+#ifdef HAVE_RANDR
+    int copy_size;
+    XRRCrtcGamma *gamma;
+
+    g_return_if_fail (crtc != NULL);
+    g_return_if_fail (red != NULL);
+    g_return_if_fail (green != NULL);
+    g_return_if_fail (blue != NULL);
+
+    if (size != crtc->gamma_size)
+	return;
+
+    gamma = XRRAllocGamma (crtc->gamma_size);
+
+    copy_size = crtc->gamma_size * sizeof (unsigned short);
+    memcpy (gamma->red, red, copy_size);
+    memcpy (gamma->green, green, copy_size);
+    memcpy (gamma->blue, blue, copy_size);
+
+    XRRSetCrtcGamma (DISPLAY (crtc), crtc->id, gamma);
+    XRRFreeGamma (gamma);
+#endif /* HAVE_RANDR */
+}
+
+/**
+ * xfce_rr_crtc_get_gamma:
+ * @crtc: a #XfceRRCrtc
+ * @size:
+ * @red: (out): the minimum width
+ * @green: (out): the maximum width
+ * @blue: (out): the minimum height
+ *
+ * Returns: %TRUE for success
+ */
+gboolean
+xfce_rr_crtc_get_gamma (XfceRRCrtc *crtc, int *size,
+			 unsigned short **red, unsigned short **green,
+			 unsigned short **blue)
+{
+#ifdef HAVE_RANDR
+    int copy_size;
+    unsigned short *r, *g, *b;
+    XRRCrtcGamma *gamma;
+
+    g_return_val_if_fail (crtc != NULL, FALSE);
+
+    gamma = XRRGetCrtcGamma (DISPLAY (crtc), crtc->id);
+    if (!gamma)
+	return FALSE;
+
+    copy_size = crtc->gamma_size * sizeof (unsigned short);
+
+    if (red) {
+	r = g_new0 (unsigned short, crtc->gamma_size);
+	memcpy (r, gamma->red, copy_size);
+	*red = r;
+    }
+
+    if (green) {
+	g = g_new0 (unsigned short, crtc->gamma_size);
+	memcpy (g, gamma->green, copy_size);
+	*green = g;
+    }
+
+    if (blue) {
+	b = g_new0 (unsigned short, crtc->gamma_size);
+	memcpy (b, gamma->blue, copy_size);
+	*blue = b;
+    }
+
+    XRRFreeGamma (gamma);
+
+    if (size)
+	*size = crtc->gamma_size;
+
+    return TRUE;
+#else
+    return FALSE;
+#endif /* HAVE_RANDR */
+}
+
diff --git a/src/xfce-rr.h b/src/xfce-rr.h
new file mode 100644
index 0000000..73e954d
--- /dev/null
+++ b/src/xfce-rr.h
@@ -0,0 +1,196 @@
+/* xfce-rr.h
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ * 
+ * This file is part of the Mate Library.
+ * 
+ * The Mate Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Mate 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
+ * Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Mate Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ * 
+ * Author: Soren Sandmann <sandmann at redhat.com>
+ */
+#ifndef XFCE_RR_H
+#define XFCE_RR_H
+
+#include <glib.h>
+#include <gdk/gdk.h>
+
+typedef struct XfceRRScreenPrivate XfceRRScreenPrivate;
+typedef struct XfceRROutput XfceRROutput;
+typedef struct XfceRRCrtc XfceRRCrtc;
+typedef struct XfceRRMode XfceRRMode;
+
+typedef struct {
+    GObject parent;
+
+    XfceRRScreenPrivate* priv;
+} XfceRRScreen;
+
+typedef struct {
+    GObjectClass parent_class;
+
+        void (* changed) (void);
+} XfceRRScreenClass;
+
+typedef enum
+{
+    XFCE_RR_ROTATION_0 =	(1 << 0),
+    XFCE_RR_ROTATION_90 =	(1 << 1),
+    XFCE_RR_ROTATION_180 =	(1 << 2),
+    XFCE_RR_ROTATION_270 =	(1 << 3),
+    XFCE_RR_REFLECT_X =	(1 << 4),
+    XFCE_RR_REFLECT_Y =	(1 << 5)
+} XfceRRRotation;
+
+/* Error codes */
+
+#define XFCE_RR_ERROR (xfce_rr_error_quark ())
+
+GQuark xfce_rr_error_quark (void);
+
+typedef enum {
+    XFCE_RR_ERROR_UNKNOWN,		/* generic "fail" */
+    XFCE_RR_ERROR_NO_RANDR_EXTENSION,	/* RANDR extension is not present */
+    XFCE_RR_ERROR_RANDR_ERROR,		/* generic/undescribed error from the underlying XRR API */
+    XFCE_RR_ERROR_BOUNDS_ERROR,	/* requested bounds of a CRTC are outside the maximum size */
+    XFCE_RR_ERROR_CRTC_ASSIGNMENT,	/* could not assign CRTCs to outputs */
+    XFCE_RR_ERROR_NO_MATCHING_CONFIG,	/* none of the saved configurations matched the current configuration */
+} XfceRRError;
+
+#define XFCE_RR_CONNECTOR_TYPE_PANEL "Panel"  /* This is a laptop's built-in LCD */
+
+#define XFCE_TYPE_RR_SCREEN                  (xfce_rr_screen_get_type())
+#define XFCE_RR_SCREEN(obj)                  (G_TYPE_CHECK_INSTANCE_CAST ((obj), XFCE_TYPE_RR_SCREEN, XfceRRScreen))
+#define XFCE_IS_RR_SCREEN(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), XFCE_TYPE_RR_SCREEN))
+#define XFCE_RR_SCREEN_CLASS(klass)          (G_TYPE_CHECK_CLASS_CAST ((klass), XFCE_TYPE_RR_SCREEN, XfceRRScreenClass))
+#define XFCE_IS_RR_SCREEN_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass), XFCE_TYPE_RR_SCREEN))
+#define XFCE_RR_SCREEN_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), XFCE_TYPE_RR_SCREEN, XfceRRScreenClass))
+
+#define XFCE_TYPE_RR_OUTPUT (xfce_rr_output_get_type())
+#define XFCE_TYPE_RR_CRTC   (xfce_rr_crtc_get_type())
+#define XFCE_TYPE_RR_MODE   (xfce_rr_mode_get_type())
+
+GType xfce_rr_screen_get_type (void);
+GType xfce_rr_output_get_type (void);
+GType xfce_rr_crtc_get_type (void);
+GType xfce_rr_mode_get_type (void);
+
+/* XfceRRScreen */
+XfceRRScreen * xfce_rr_screen_new                (GdkScreen             *screen,
+						    GError               **error);
+XfceRROutput **xfce_rr_screen_list_outputs       (XfceRRScreen         *screen);
+XfceRRCrtc **  xfce_rr_screen_list_crtcs         (XfceRRScreen         *screen);
+XfceRRMode **  xfce_rr_screen_list_modes         (XfceRRScreen         *screen);
+XfceRRMode **  xfce_rr_screen_list_clone_modes   (XfceRRScreen	  *screen);
+void            xfce_rr_screen_set_size           (XfceRRScreen         *screen,
+						    int                    width,
+						    int                    height,
+						    int                    mm_width,
+						    int                    mm_height);
+XfceRRCrtc *   xfce_rr_screen_get_crtc_by_id     (XfceRRScreen         *screen,
+						    guint32                id);
+gboolean        xfce_rr_screen_refresh            (XfceRRScreen         *screen,
+						    GError               **error);
+XfceRROutput * xfce_rr_screen_get_output_by_id   (XfceRRScreen         *screen,
+						    guint32                id);
+XfceRROutput * xfce_rr_screen_get_output_by_name (XfceRRScreen         *screen,
+						    const char            *name);
+void            xfce_rr_screen_get_ranges         (XfceRRScreen         *screen,
+						    int                   *min_width,
+						    int                   *max_width,
+						    int                   *min_height,
+						    int                   *max_height);
+void            xfce_rr_screen_get_timestamps     (XfceRRScreen         *screen,
+						    guint32               *change_timestamp_ret,
+						    guint32               *config_timestamp_ret);
+
+void            xfce_rr_screen_set_primary_output (XfceRRScreen         *screen,
+                                                    XfceRROutput         *output);
+
+/* XfceRROutput */
+guint32         xfce_rr_output_get_id             (XfceRROutput         *output);
+const char *    xfce_rr_output_get_name           (XfceRROutput         *output);
+gboolean        xfce_rr_output_is_connected       (XfceRROutput         *output);
+int             xfce_rr_output_get_size_inches    (XfceRROutput         *output);
+int             xfce_rr_output_get_width_mm       (XfceRROutput         *outout);
+int             xfce_rr_output_get_height_mm      (XfceRROutput         *output);
+const guint8 *  xfce_rr_output_get_edid_data      (XfceRROutput         *output);
+XfceRRCrtc **  xfce_rr_output_get_possible_crtcs (XfceRROutput         *output);
+XfceRRMode *   xfce_rr_output_get_current_mode   (XfceRROutput         *output);
+XfceRRCrtc *   xfce_rr_output_get_crtc           (XfceRROutput         *output);
+const char *    xfce_rr_output_get_connector_type (XfceRROutput         *output);
+gboolean        xfce_rr_output_is_laptop          (XfceRROutput         *output);
+void            xfce_rr_output_get_position       (XfceRROutput         *output,
+						    int                   *x,
+						    int                   *y);
+gboolean        xfce_rr_output_can_clone          (XfceRROutput         *output,
+						    XfceRROutput         *clone);
+XfceRRMode **  xfce_rr_output_list_modes         (XfceRROutput         *output);
+XfceRRMode *   xfce_rr_output_get_preferred_mode (XfceRROutput         *output);
+gboolean        xfce_rr_output_supports_mode      (XfceRROutput         *output,
+						    XfceRRMode           *mode);
+gboolean        xfce_rr_output_get_is_primary     (XfceRROutput         *output);
+
+/* XfceRRMode */
+guint32         xfce_rr_mode_get_id               (XfceRRMode           *mode);
+guint           xfce_rr_mode_get_width            (XfceRRMode           *mode);
+guint           xfce_rr_mode_get_height           (XfceRRMode           *mode);
+int             xfce_rr_mode_get_freq             (XfceRRMode           *mode);
+
+/* XfceRRCrtc */
+guint32         xfce_rr_crtc_get_id               (XfceRRCrtc           *crtc);
+
+#ifndef MATE_DISABLE_DEPRECATED
+gboolean        xfce_rr_crtc_set_config           (XfceRRCrtc           *crtc,
+						    int                    x,
+						    int                    y,
+						    XfceRRMode           *mode,
+						    XfceRRRotation        rotation,
+						    XfceRROutput        **outputs,
+						    int                    n_outputs,
+						    GError               **error);
+#endif
+
+gboolean        xfce_rr_crtc_set_config_with_time (XfceRRCrtc           *crtc,
+						    guint32                timestamp,
+						    int                    x,
+						    int                    y,
+						    XfceRRMode           *mode,
+						    XfceRRRotation        rotation,
+						    XfceRROutput        **outputs,
+						    int                    n_outputs,
+						    GError               **error);
+gboolean        xfce_rr_crtc_can_drive_output     (XfceRRCrtc           *crtc,
+						    XfceRROutput         *output);
+XfceRRMode *   xfce_rr_crtc_get_current_mode     (XfceRRCrtc           *crtc);
+void            xfce_rr_crtc_get_position         (XfceRRCrtc           *crtc,
+						    int                   *x,
+						    int                   *y);
+XfceRRRotation xfce_rr_crtc_get_current_rotation (XfceRRCrtc           *crtc);
+XfceRRRotation xfce_rr_crtc_get_rotations        (XfceRRCrtc           *crtc);
+gboolean        xfce_rr_crtc_supports_rotation    (XfceRRCrtc           *crtc,
+						    XfceRRRotation        rotation);
+
+gboolean        xfce_rr_crtc_get_gamma            (XfceRRCrtc           *crtc,
+						    int                   *size,
+						    unsigned short       **red,
+						    unsigned short       **green,
+						    unsigned short       **blue);
+void            xfce_rr_crtc_set_gamma            (XfceRRCrtc           *crtc,
+						    int                    size,
+						    unsigned short        *red,
+						    unsigned short        *green,
+						    unsigned short        *blue);
+#endif /* XFCE_RR_H */
diff --git a/src/xfcekbd-config-private.h b/src/xfcekbd-config-private.h
new file mode 100644
index 0000000..fe46cda
--- /dev/null
+++ b/src/xfcekbd-config-private.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2006 Sergey V. Udaltsov <svu at gnome.org>
+ *
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __XFCEKBD_CONFIG_PRIVATE_H__
+#define __XFCEKBD_CONFIG_PRIVATE_H__
+
+#include "xfcekbd-desktop-config.h"
+#include "xfcekbd-keyboard-config.h"
+
+#define XFCEKBD_CONFIG_SCHEMA "org.mate.peripherals-keyboard-xkb"
+
+extern const gchar MATEKBD_PREVIEW_CONFIG_KEY_X[];
+extern const gchar MATEKBD_PREVIEW_CONFIG_KEY_Y[];
+extern const gchar MATEKBD_PREVIEW_CONFIG_KEY_WIDTH[];
+extern const gchar MATEKBD_PREVIEW_CONFIG_KEY_HEIGHT[];
+
+/**
+ * General config functions (private)
+ */
+extern void xfcekbd_keyboard_config_model_set (XfcekbdKeyboardConfig *
+					    kbd_config,
+					    const gchar * model_name);
+
+extern void xfcekbd_keyboard_config_options_set (XfcekbdKeyboardConfig *
+					      kbd_config,
+					      gint idx,
+					      const gchar * group_name,
+					      const gchar * option_name);
+extern gboolean xfcekbd_keyboard_config_options_is_set (XfcekbdKeyboardConfig *
+						     kbd_config,
+						     const gchar *
+						     group_name,
+						     const gchar *
+						     option_name);
+
+extern gboolean xfcekbd_keyboard_config_dump_settings (XfcekbdKeyboardConfig *
+						    kbd_config,
+						    const char *file_name);
+
+extern void xfcekbd_keyboard_config_start_listen (XfcekbdKeyboardConfig *
+					       kbd_config,
+					       GCallback func,
+					       gpointer user_data);
+
+extern void xfcekbd_keyboard_config_stop_listen (XfcekbdKeyboardConfig *
+					      kbd_config);
+
+extern gboolean xfcekbd_keyboard_config_get_lv_descriptions (XklConfigRegistry
+							  *
+							  config_registry,
+							  const gchar *
+							  layout_name,
+							  const gchar *
+							  variant_name,
+							  gchar **
+							  layout_short_descr,
+							  gchar **
+							  layout_descr,
+							  gchar **
+							  variant_short_descr,
+							  gchar **
+							  variant_descr);
+
+#endif
diff --git a/src/xfcekbd-desktop-config.c b/src/xfcekbd-desktop-config.c
new file mode 100644
index 0000000..f92abcd
--- /dev/null
+++ b/src/xfcekbd-desktop-config.c
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2006 Sergey V. Udaltsov <svu at gnome.org>
+ *
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <X11/keysym.h>
+
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+#include "xfcekbd-desktop-config.h"
+#include "xfcekbd-config-private.h"
+
+/**
+ * XfcekbdDesktopConfig:
+ */
+#define XFCEKBD_DESKTOP_CONFIG_SCHEMA  XFCEKBD_CONFIG_SCHEMA ".general"
+
+const gchar XFCEKBD_DESKTOP_CONFIG_KEY_DEFAULT_GROUP[] = "default-group";
+const gchar XFCEKBD_DESKTOP_CONFIG_KEY_GROUP_PER_WINDOW[] = "group-per-window";
+const gchar XFCEKBD_DESKTOP_CONFIG_KEY_HANDLE_INDICATORS[] = "handle-indicators";
+const gchar XFCEKBD_DESKTOP_CONFIG_KEY_LAYOUT_NAMES_AS_GROUP_NAMES[] = "layout-names-as-group-names";
+const gchar XFCEKBD_DESKTOP_CONFIG_KEY_LOAD_EXTRA_ITEMS[] = "load-extra-items";
+
+/*
+ * static common functions
+ */
+
+static gboolean
+    xfcekbd_desktop_config_get_lv_descriptions
+    (XfcekbdDesktopConfig * config,
+     XklConfigRegistry * registry,
+     const gchar ** layout_ids,
+     const gchar ** variant_ids,
+     gchar *** short_layout_descriptions,
+     gchar *** long_layout_descriptions,
+     gchar *** short_variant_descriptions,
+     gchar *** long_variant_descriptions) {
+	const gchar **pl, **pv;
+	guint total_layouts;
+	gchar **sld, **lld, **svd, **lvd;
+	XklConfigItem *item = xkl_config_item_new ();
+
+	if (!
+	    (xkl_engine_get_features (config->engine) &
+	     XKLF_MULTIPLE_LAYOUTS_SUPPORTED))
+		return FALSE;
+
+	pl = layout_ids;
+	pv = variant_ids;
+	total_layouts = g_strv_length ((char **) layout_ids);
+	sld = *short_layout_descriptions =
+	    g_new0 (gchar *, total_layouts + 1);
+	lld = *long_layout_descriptions =
+	    g_new0 (gchar *, total_layouts + 1);
+	svd = *short_variant_descriptions =
+	    g_new0 (gchar *, total_layouts + 1);
+	lvd = *long_variant_descriptions =
+	    g_new0 (gchar *, total_layouts + 1);
+
+	while (pl != NULL && *pl != NULL) {
+
+		xkl_debug (100, "ids: [%s][%s]\n", *pl,
+			   pv == NULL ? NULL : *pv);
+
+		g_snprintf (item->name, sizeof item->name, "%s", *pl);
+		if (xkl_config_registry_find_layout (registry, item)) {
+			*sld = g_strdup (item->short_description);
+			*lld = g_strdup (item->description);
+		} else {
+			*sld = g_strdup ("");
+			*lld = g_strdup ("");
+		}
+
+		if (pv != NULL && *pv != NULL) {
+			g_snprintf (item->name, sizeof item->name, "%s",
+				    *pv);
+			if (xkl_config_registry_find_variant
+			    (registry, *pl, item)) {
+				*svd = g_strdup (item->short_description);
+				*lvd = g_strdup (item->description);
+			} else {
+				*svd = g_strdup ("");
+				*lvd = g_strdup ("");
+			}
+		} else {
+			*svd = g_strdup ("");
+			*lvd = g_strdup ("");
+		}
+
+		xkl_debug (100, "description: [%s][%s][%s][%s]\n",
+			   *sld, *lld, *svd, *lvd);
+		sld++;
+		lld++;
+		svd++;
+		lvd++;
+
+		pl++;
+
+		if (pv != NULL && *pv != NULL)
+			pv++;
+	}
+
+	g_object_unref (item);
+	return TRUE;
+}
+
+/*
+ * extern XfcekbdDesktopConfig config functions
+ */
+void
+xfcekbd_desktop_config_init (XfcekbdDesktopConfig * config,
+			  XklEngine * engine)
+{
+	memset (config, 0, sizeof (*config));
+	config->settings = g_settings_new (XFCEKBD_DESKTOP_CONFIG_SCHEMA);
+	config->engine = engine;
+}
+
+void
+xfcekbd_desktop_config_term (XfcekbdDesktopConfig * config)
+{
+	g_object_unref (config->settings);
+	config->settings = NULL;
+}
+
+void
+xfcekbd_desktop_config_load_from_gsettings (XfcekbdDesktopConfig * config)
+{
+	config->group_per_app =
+	    g_settings_get_boolean (config->settings,
+				 XFCEKBD_DESKTOP_CONFIG_KEY_GROUP_PER_WINDOW);
+	xkl_debug (150, "group_per_app: %d\n", config->group_per_app);
+
+	config->handle_indicators =
+	    g_settings_get_boolean (config->settings,
+				 XFCEKBD_DESKTOP_CONFIG_KEY_HANDLE_INDICATORS);
+	xkl_debug (150, "handle_indicators: %d\n",
+		   config->handle_indicators);
+
+	config->layout_names_as_group_names =
+	    g_settings_get_boolean (config->settings,
+				   XFCEKBD_DESKTOP_CONFIG_KEY_LAYOUT_NAMES_AS_GROUP_NAMES);
+	xkl_debug (150, "layout_names_as_group_names: %d\n",
+		   config->layout_names_as_group_names);
+
+	config->load_extra_items =
+	    g_settings_get_boolean (config->settings,
+				   XFCEKBD_DESKTOP_CONFIG_KEY_LOAD_EXTRA_ITEMS);
+	xkl_debug (150, "load_extra_items: %d\n",
+		   config->load_extra_items);
+
+	config->default_group =
+	    g_settings_get_int (config->settings,
+				  XFCEKBD_DESKTOP_CONFIG_KEY_DEFAULT_GROUP);
+
+	if (config->default_group < -1
+	    || config->default_group >=
+	    xkl_engine_get_max_num_groups (config->engine))
+		config->default_group = -1;
+	xkl_debug (150, "default_group: %d\n", config->default_group);
+}
+
+void
+xfcekbd_desktop_config_save_to_gsettings (XfcekbdDesktopConfig * config)
+{
+	g_settings_delay (config->settings);
+
+	g_settings_set_boolean (config->settings,
+			     XFCEKBD_DESKTOP_CONFIG_KEY_GROUP_PER_WINDOW,
+			     config->group_per_app);
+	g_settings_set_boolean (config->settings,
+			     XFCEKBD_DESKTOP_CONFIG_KEY_HANDLE_INDICATORS,
+			     config->handle_indicators);
+	g_settings_set_boolean (config->settings,
+			     XFCEKBD_DESKTOP_CONFIG_KEY_LAYOUT_NAMES_AS_GROUP_NAMES,
+			     config->layout_names_as_group_names);
+	g_settings_set_boolean (config->settings,
+			     XFCEKBD_DESKTOP_CONFIG_KEY_LOAD_EXTRA_ITEMS,
+			     config->load_extra_items);
+	g_settings_set_int (config->settings,
+			    XFCEKBD_DESKTOP_CONFIG_KEY_DEFAULT_GROUP,
+			    config->default_group);
+
+	g_settings_apply (config->settings);
+}
+
+gboolean
+xfcekbd_desktop_config_activate (XfcekbdDesktopConfig * config)
+{
+	gboolean rv = TRUE;
+
+	xkl_engine_set_group_per_toplevel_window (config->engine,
+						  config->group_per_app);
+	xkl_engine_set_indicators_handling (config->engine,
+					    config->handle_indicators);
+	xkl_engine_set_default_group (config->engine,
+				      config->default_group);
+
+	return rv;
+}
+
+void
+xfcekbd_desktop_config_lock_next_group (XfcekbdDesktopConfig * config)
+{
+	int group = xkl_engine_get_next_group (config->engine);
+	xkl_engine_lock_group (config->engine, group);
+}
+
+void
+xfcekbd_desktop_config_lock_prev_group (XfcekbdDesktopConfig * config)
+{
+	int group = xkl_engine_get_prev_group (config->engine);
+	xkl_engine_lock_group (config->engine, group);
+}
+
+void
+xfcekbd_desktop_config_restore_group (XfcekbdDesktopConfig * config)
+{
+	int group = xkl_engine_get_current_window_group (config->engine);
+	xkl_engine_lock_group (config->engine, group);
+}
+
+/**
+ * xfcekbd_desktop_config_start_listen:
+ * @func: (scope notified): a function to call when settings are changed
+ */
+void
+xfcekbd_desktop_config_start_listen (XfcekbdDesktopConfig * config,
+				  GCallback func,
+				  gpointer user_data)
+{
+	config->config_listener_id =
+	    g_signal_connect (config->settings, "changed", func,
+			      user_data);
+}
+
+void
+xfcekbd_desktop_config_stop_listen (XfcekbdDesktopConfig * config)
+{
+	g_signal_handler_disconnect (config->settings,
+				    config->config_listener_id);
+	config->config_listener_id = 0;
+}
+
+gboolean
+xfcekbd_desktop_config_load_group_descriptions (XfcekbdDesktopConfig
+					     * config,
+					     XklConfigRegistry *
+					     registry,
+					     const gchar **
+					     layout_ids,
+					     const gchar **
+					     variant_ids,
+					     gchar ***
+					     short_group_names,
+					     gchar *** full_group_names)
+{
+	gchar **sld, **lld, **svd, **lvd;
+	gchar **psld, **plld, **plvd;
+	gchar **psgn, **pfgn, **psvd;
+	gint total_descriptions;
+
+	if (!xfcekbd_desktop_config_get_lv_descriptions
+	    (config, registry, layout_ids, variant_ids, &sld, &lld, &svd,
+	     &lvd)) {
+		return False;
+	}
+
+	total_descriptions = g_strv_length (sld);
+
+	*short_group_names = psgn =
+	    g_new0 (gchar *, total_descriptions + 1);
+	*full_group_names = pfgn =
+	    g_new0 (gchar *, total_descriptions + 1);
+
+	plld = lld;
+	psld = sld;
+	plvd = lvd;
+	psvd = svd;
+	while (plld != NULL && *plld != NULL) {
+		gchar *sd = (*psvd[0] == '\0') ? *psld : *psvd;
+		psld++, psvd++;
+		*psgn++ = g_strdup (sd);
+		*pfgn++ = g_strdup (xfcekbd_keyboard_config_format_full_layout
+				    (*plld++, *plvd++));
+	}
+	g_strfreev (sld);
+	g_strfreev (lld);
+	g_strfreev (svd);
+	g_strfreev (lvd);
+
+	return True;
+}
diff --git a/src/xfcekbd-desktop-config.h b/src/xfcekbd-desktop-config.h
new file mode 100644
index 0000000..8753991
--- /dev/null
+++ b/src/xfcekbd-desktop-config.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2006 Sergey V. Udaltsov <svu at gnome.org>
+ *
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __XFCEKBD_DESKTOP_CONFIG_H__
+#define __XFCEKBD_DESKTOP_CONFIG_H__
+
+#include <X11/Xlib.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <libxklavier/xklavier.h>
+
+extern const gchar XFCEKBD_DESKTOP_CONFIG_KEY_DEFAULT_GROUP[];
+extern const gchar XFCEKBD_DESKTOP_CONFIG_KEY_GROUP_PER_WINDOW[];
+extern const gchar XFCEKBD_DESKTOP_CONFIG_KEY_HANDLE_INDICATORS[];
+extern const gchar XFCEKBD_DESKTOP_CONFIG_KEY_LAYOUT_NAMES_AS_GROUP_NAMES[];
+
+/*
+ * General configuration
+ */
+typedef struct _XfcekbdDesktopConfig XfcekbdDesktopConfig;
+struct _XfcekbdDesktopConfig {
+	gint default_group;
+	gboolean group_per_app;
+	gboolean handle_indicators;
+	gboolean layout_names_as_group_names;
+	gboolean load_extra_items;
+
+	/* private, transient */
+	GSettings *settings;
+	int config_listener_id;
+	XklEngine *engine;
+};
+
+/*
+ * XfcekbdDesktopConfig functions
+ */
+extern void xfcekbd_desktop_config_init (XfcekbdDesktopConfig * config,
+				      XklEngine * engine);
+extern void xfcekbd_desktop_config_term (XfcekbdDesktopConfig * config);
+
+extern void xfcekbd_desktop_config_load_from_gsettings (XfcekbdDesktopConfig *
+						 config);
+
+extern void xfcekbd_desktop_config_save_to_gsettings (XfcekbdDesktopConfig * config);
+
+extern gboolean xfcekbd_desktop_config_activate (XfcekbdDesktopConfig * config);
+
+extern gboolean
+xfcekbd_desktop_config_load_group_descriptions (XfcekbdDesktopConfig
+					     * config,
+					     XklConfigRegistry *
+					     registry,
+					     const gchar **
+					     layout_ids,
+					     const gchar **
+					     variant_ids,
+					     gchar ***
+					     short_group_names,
+					     gchar *** full_group_names);
+
+extern void xfcekbd_desktop_config_lock_next_group (XfcekbdDesktopConfig *
+						 config);
+
+extern void xfcekbd_desktop_config_lock_prev_group (XfcekbdDesktopConfig *
+						 config);
+
+extern void xfcekbd_desktop_config_restore_group (XfcekbdDesktopConfig * config);
+
+extern void xfcekbd_desktop_config_start_listen (XfcekbdDesktopConfig * config,
+					      GCallback func,
+					      gpointer user_data);
+
+extern void xfcekbd_desktop_config_stop_listen (XfcekbdDesktopConfig * config);
+
+#endif
diff --git a/src/xfcekbd-indicator-config.c b/src/xfcekbd-indicator-config.c
new file mode 100644
index 0000000..0ab8d6e
--- /dev/null
+++ b/src/xfcekbd-indicator-config.c
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2006 Sergey V. Udaltsov <svu at gnome.org>
+ *
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <X11/keysym.h>
+
+#include <pango/pango.h>
+
+#include <glib/gi18n-lib.h>
+#include <gdk/gdkx.h>
+
+#include "xfcekbd-keyboard-config.h"
+#include "xfcekbd-indicator-config.h"
+
+#include "xfcekbd-config-private.h"
+
+/**
+ * XfcekbdIndicatorConfig:
+ */
+#define XFCEKBD_INDICATOR_CONFIG_SCHEMA  XFCEKBD_CONFIG_SCHEMA ".indicator"
+
+const gchar XFCEKBD_INDICATOR_CONFIG_KEY_SHOW_FLAGS[] = "show-flags";
+const gchar XFCEKBD_INDICATOR_CONFIG_KEY_SECONDARIES[] = "secondary";
+const gchar XFCEKBD_INDICATOR_CONFIG_KEY_FONT_FAMILY[] = "font-family";
+const gchar XFCEKBD_INDICATOR_CONFIG_KEY_FOREGROUND_COLOR[] = "foreground-color";
+const gchar XFCEKBD_INDICATOR_CONFIG_KEY_BACKGROUND_COLOR[] = "background-color";
+
+#define SYSTEM_FONT_SCHEMA "org.mate.interface"
+#define SYSTEM_FONT_KEY "font-name"
+
+/*
+ * static applet config functions
+ */
+static void
+xfcekbd_indicator_config_load_font (XfcekbdIndicatorConfig * ind_config)
+{
+	ind_config->font_family =
+	    g_settings_get_string (ind_config->settings,
+				   XFCEKBD_INDICATOR_CONFIG_KEY_FONT_FAMILY);
+
+	if (ind_config->font_family == NULL ||
+	    ind_config->font_family[0] == '\0') {
+		PangoFontDescription *fd = NULL;
+		GtkWidgetPath *widget_path = gtk_widget_path_new ();
+		GtkStyleContext *context = gtk_style_context_new ();
+
+		gtk_widget_path_append_type (widget_path, GTK_TYPE_WINDOW);
+		gtk_widget_path_iter_set_name (widget_path, -1 , "PanelWidget");
+
+		gtk_style_context_set_path (context, widget_path);
+		gtk_style_context_set_screen (context, gdk_screen_get_default ());
+		gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
+		gtk_style_context_add_class (context, GTK_STYLE_CLASS_DEFAULT);
+		gtk_style_context_add_class (context, "gnome-panel-menu-bar");
+		gtk_style_context_add_class (context, "mate-panel-menu-bar");
+
+		gtk_style_context_get (context, GTK_STATE_FLAG_NORMAL,
+		                       GTK_STYLE_PROPERTY_FONT, &fd, NULL);
+
+		if (fd != NULL) {
+			ind_config->font_family =
+			    g_strdup (pango_font_description_to_string(fd));
+		}
+
+		g_object_unref (G_OBJECT (context));
+		gtk_widget_path_unref (widget_path);
+	}
+	xkl_debug (150, "font: [%s]\n", ind_config->font_family);
+
+}
+
+static void
+xfcekbd_indicator_config_load_colors (XfcekbdIndicatorConfig * ind_config)
+{
+	ind_config->foreground_color =
+	    g_settings_get_string (ind_config->settings,
+	                           XFCEKBD_INDICATOR_CONFIG_KEY_FOREGROUND_COLOR);
+
+	if (ind_config->foreground_color == NULL ||
+	    ind_config->foreground_color[0] == '\0') {
+		GtkWidgetPath *widget_path = gtk_widget_path_new ();
+		GtkStyleContext *context = gtk_style_context_new ();
+		GdkRGBA fg_color;
+
+		gtk_widget_path_append_type (widget_path, GTK_TYPE_WINDOW);
+		gtk_widget_path_iter_set_name (widget_path, -1 , "PanelWidget");
+
+		gtk_style_context_set_path (context, widget_path);
+		gtk_style_context_set_screen (context, gdk_screen_get_default ());
+		gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
+		gtk_style_context_add_class (context, GTK_STYLE_CLASS_DEFAULT);
+		gtk_style_context_add_class (context, "gnome-panel-menu-bar");
+		gtk_style_context_add_class (context, "mate-panel-menu-bar");
+
+		gtk_style_context_get_color (context,
+		                             GTK_STATE_FLAG_NORMAL, &fg_color);
+		ind_config->foreground_color =
+		    g_strdup_printf ("%g %g %g",
+		                     fg_color.red,
+		                     fg_color.green,
+		                     fg_color.blue);
+
+		g_object_unref (G_OBJECT (context));
+		gtk_widget_path_unref (widget_path);
+	}
+
+	ind_config->background_color =
+	    g_settings_get_string (ind_config->settings,
+				     XFCEKBD_INDICATOR_CONFIG_KEY_BACKGROUND_COLOR);
+}
+
+void
+xfcekbd_indicator_config_refresh_style (XfcekbdIndicatorConfig * ind_config)
+{
+	g_free (ind_config->font_family);
+	g_free (ind_config->foreground_color);
+	g_free (ind_config->background_color);
+	xfcekbd_indicator_config_load_font (ind_config);
+	xfcekbd_indicator_config_load_colors (ind_config);
+}
+
+gchar *
+xfcekbd_indicator_config_get_images_file (XfcekbdIndicatorConfig *
+				       ind_config,
+				       XfcekbdKeyboardConfig *
+				       kbd_config, int group)
+{
+	char *image_file = NULL;
+	GtkIconInfo *icon_info = NULL;
+
+	if (!ind_config->show_flags)
+		return NULL;
+
+	if ((kbd_config->layouts_variants != NULL) &&
+	    (g_strv_length (kbd_config->layouts_variants) > group)) {
+		char *full_layout_name =
+		    kbd_config->layouts_variants[group];
+
+		if (full_layout_name != NULL) {
+			char *l, *v;
+			xfcekbd_keyboard_config_split_items (full_layout_name,
+							  &l, &v);
+			if (l != NULL) {
+				/* probably there is something in theme? */
+				icon_info = gtk_icon_theme_lookup_icon
+				    (ind_config->icon_theme, l, 48, 0);
+
+				/* Unbelievable but happens */
+				if (icon_info != NULL &&
+				    gtk_icon_info_get_filename (icon_info) == NULL) {
+					g_object_unref (icon_info);
+					icon_info = NULL;
+				}
+			}
+		}
+	}
+	/* fallback to the default value */
+	if (icon_info == NULL) {
+		icon_info = gtk_icon_theme_lookup_icon
+		    (ind_config->icon_theme, "stock_dialog-error", 48, 0);
+	}
+	if (icon_info != NULL) {
+		image_file =
+		    g_strdup (gtk_icon_info_get_filename (icon_info));
+		g_object_unref (icon_info);
+	}
+
+	return image_file;
+}
+
+void
+xfcekbd_indicator_config_load_image_filenames (XfcekbdIndicatorConfig *
+					    ind_config,
+					    XfcekbdKeyboardConfig *
+					    kbd_config)
+{
+	int i;
+	ind_config->image_filenames = NULL;
+
+	if (!ind_config->show_flags)
+		return;
+
+	for (i = xkl_engine_get_max_num_groups (ind_config->engine);
+	     --i >= 0;) {
+		gchar *image_file =
+		    xfcekbd_indicator_config_get_images_file (ind_config,
+							   kbd_config,
+							   i);
+		ind_config->image_filenames =
+		    g_slist_prepend (ind_config->image_filenames,
+				     image_file);
+	}
+}
+
+void
+xfcekbd_indicator_config_free_image_filenames (XfcekbdIndicatorConfig *
+					    ind_config)
+{
+	while (ind_config->image_filenames) {
+		if (ind_config->image_filenames->data)
+			g_free (ind_config->image_filenames->data);
+		ind_config->image_filenames =
+		    g_slist_delete_link (ind_config->image_filenames,
+					 ind_config->image_filenames);
+	}
+}
+
+void
+xfcekbd_indicator_config_init (XfcekbdIndicatorConfig * ind_config,
+			       XklEngine * engine)
+{
+	gchar *sp;
+
+	memset (ind_config, 0, sizeof (*ind_config));
+	ind_config->settings = g_settings_new (XFCEKBD_INDICATOR_CONFIG_SCHEMA);
+	ind_config->engine = engine;
+
+	ind_config->icon_theme = gtk_icon_theme_get_default ();
+
+	gtk_icon_theme_append_search_path (ind_config->icon_theme, sp =
+					   g_build_filename (g_get_home_dir
+							     (),
+							     ".icons/flags",
+							     NULL));
+	g_free (sp);
+
+	gtk_icon_theme_append_search_path (ind_config->icon_theme,
+					   sp =
+					   g_build_filename (DATADIR,
+							     "pixmaps/flags",
+							     NULL));
+	g_free (sp);
+
+	gtk_icon_theme_append_search_path (ind_config->icon_theme,
+					   sp =
+					   g_build_filename (DATADIR,
+							     "icons/flags",
+							     NULL));
+	g_free (sp);
+}
+
+void
+xfcekbd_indicator_config_term (XfcekbdIndicatorConfig * ind_config)
+{
+	g_free (ind_config->font_family);
+	ind_config->font_family = NULL;
+
+	g_free (ind_config->foreground_color);
+	ind_config->foreground_color = NULL;
+
+	g_free (ind_config->background_color);
+	ind_config->background_color = NULL;
+
+	ind_config->icon_theme = NULL;
+
+	xfcekbd_indicator_config_free_image_filenames (ind_config);
+
+	g_object_unref (ind_config->settings);
+	ind_config->settings = NULL;
+}
+
+void
+xfcekbd_indicator_config_load_from_gsettings (XfcekbdIndicatorConfig * ind_config)
+{
+	ind_config->secondary_groups_mask =
+	    g_settings_get_int (ind_config->settings,
+				XFCEKBD_INDICATOR_CONFIG_KEY_SECONDARIES);
+
+	ind_config->show_flags =
+	    g_settings_get_boolean (ind_config->settings,
+				 XFCEKBD_INDICATOR_CONFIG_KEY_SHOW_FLAGS);
+
+	xfcekbd_indicator_config_load_font (ind_config);
+	xfcekbd_indicator_config_load_colors (ind_config);
+
+}
+
+void
+xfcekbd_indicator_config_save_to_gsettings (XfcekbdIndicatorConfig * ind_config)
+{
+	g_settings_delay (ind_config->settings);
+
+	g_settings_set_int (ind_config->settings,
+				  XFCEKBD_INDICATOR_CONFIG_KEY_SECONDARIES,
+				  ind_config->secondary_groups_mask);
+	g_settings_set_boolean (ind_config->settings,
+				   XFCEKBD_INDICATOR_CONFIG_KEY_SHOW_FLAGS,
+				   ind_config->show_flags);
+
+	g_settings_apply (ind_config->settings);
+}
+
+void
+xfcekbd_indicator_config_activate (XfcekbdIndicatorConfig * ind_config)
+{
+	xkl_engine_set_secondary_groups_mask (ind_config->engine,
+					      ind_config->secondary_groups_mask);
+}
+
+/**
+ * xfcekbd_indicator_config_start_listen:
+ * @func: (scope notified): a function to call when settings are changed
+ */
+void
+xfcekbd_indicator_config_start_listen (XfcekbdIndicatorConfig *
+				    ind_config,
+				    GCallback func,
+				    gpointer user_data)
+{
+	ind_config->config_listener_id =
+	    g_signal_connect (ind_config->settings, "changed", func,
+			      user_data);
+}
+
+void
+xfcekbd_indicator_config_stop_listen (XfcekbdIndicatorConfig * ind_config)
+{
+	g_signal_handler_disconnect (ind_config->settings,
+				     ind_config->config_listener_id);
+	ind_config->config_listener_id = 0;
+}
diff --git a/src/xfcekbd-indicator-config.h b/src/xfcekbd-indicator-config.h
new file mode 100644
index 0000000..29284a7
--- /dev/null
+++ b/src/xfcekbd-indicator-config.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2006 Sergey V. Udaltsov <svu at gnome.org>
+ *
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __XFCEKBD_INDICATOR_CONFIG_H__
+#define __XFCEKBD_INDICATOR_CONFIG_H__
+
+#include <gtk/gtk.h>
+
+#include "xfcekbd-keyboard-config.h"
+
+/*
+ * Indicator configuration
+ */
+typedef struct _XfcekbdIndicatorConfig XfcekbdIndicatorConfig;
+struct _XfcekbdIndicatorConfig {
+	int secondary_groups_mask;
+	gboolean show_flags;
+
+	gchar *font_family;
+	gchar *foreground_color;
+	gchar *background_color;
+
+	/* private, transient */
+	GSettings *settings;
+	GSList *image_filenames;
+	GtkIconTheme *icon_theme;
+	int config_listener_id;
+	XklEngine *engine;
+};
+
+/*
+ * XfcekbdIndicatorConfig functions -
+ * some of them require XfcekbdKeyboardConfig as well -
+ * for loading approptiate images
+ */
+void xfcekbd_indicator_config_init (XfcekbdIndicatorConfig *
+					applet_config,
+					XklEngine * engine);
+void xfcekbd_indicator_config_term (XfcekbdIndicatorConfig *
+					applet_config);
+
+void xfcekbd_indicator_config_load_from_gsettings (XfcekbdIndicatorConfig
+						   * applet_config);
+void xfcekbd_indicator_config_save_to_gsettings (XfcekbdIndicatorConfig *
+						 applet_config);
+
+void xfcekbd_indicator_config_refresh_style (XfcekbdIndicatorConfig *
+						 applet_config);
+
+gchar* xfcekbd_indicator_config_get_images_file (XfcekbdIndicatorConfig *
+					     applet_config,
+					     XfcekbdKeyboardConfig *
+					     kbd_config, int group);
+
+void xfcekbd_indicator_config_load_image_filenames (XfcekbdIndicatorConfig
+							* applet_config,
+							XfcekbdKeyboardConfig
+							* kbd_config);
+void xfcekbd_indicator_config_free_image_filenames (XfcekbdIndicatorConfig
+							* applet_config);
+
+/* Should be updated on Indicator/GSettings configuration change */
+void xfcekbd_indicator_config_activate (XfcekbdIndicatorConfig *
+					    applet_config);
+
+void xfcekbd_indicator_config_start_listen (XfcekbdIndicatorConfig *
+						applet_config,
+						GCallback
+						func, gpointer user_data);
+
+void xfcekbd_indicator_config_stop_listen (XfcekbdIndicatorConfig *
+					       applet_config);
+
+#endif
diff --git a/src/xfcekbd-indicator-marshal.list b/src/xfcekbd-indicator-marshal.list
new file mode 100644
index 0000000..5b76282
--- /dev/null
+++ b/src/xfcekbd-indicator-marshal.list
@@ -0,0 +1 @@
+VOID:VOID
diff --git a/src/xfcekbd-indicator.c b/src/xfcekbd-indicator.c
new file mode 100644
index 0000000..bd01be5
--- /dev/null
+++ b/src/xfcekbd-indicator.c
@@ -0,0 +1,918 @@
+/*
+ * Copyright (C) 2006 Sergey V. Udaltsov <svu at gnome.org>
+ *
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#include <config.h>
+
+#include <memory.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+#include <glib/gi18n-lib.h>
+
+#include "xfcekbd-indicator.h"
+#include "xfcekbd-indicator-marshal.h"
+
+#include "xfcekbd-desktop-config.h"
+#include "xfcekbd-indicator-config.h"
+
+typedef struct _gki_globals {
+	XklEngine *engine;
+	XklConfigRegistry *registry;
+
+	XfcekbdDesktopConfig cfg;
+	XfcekbdIndicatorConfig ind_cfg;
+	XfcekbdKeyboardConfig kbd_cfg;
+
+	const gchar *tooltips_format;
+	gchar **full_group_names;
+	gchar **short_group_names;
+	GSList *widget_instances;
+	GSList *images;
+} gki_globals;
+
+struct _XfcekbdIndicatorPrivate {
+	gboolean set_parent_tooltips;
+	gdouble angle;
+};
+
+/* one instance for ALL widgets */
+static gki_globals globals;
+
+#define ForAllIndicators() \
+	{ \
+		GSList* cur; \
+		for (cur = globals.widget_instances; cur != NULL; cur = cur->next) { \
+			XfcekbdIndicator * gki = (XfcekbdIndicator*)cur->data;
+#define NextIndicator() \
+		} \
+	}
+
+G_DEFINE_TYPE (XfcekbdIndicator, xfcekbd_indicator, GTK_TYPE_NOTEBOOK)
+
+static void
+xfcekbd_indicator_global_init (void);
+static void
+xfcekbd_indicator_global_term (void);
+static GtkWidget *
+xfcekbd_indicator_prepare_drawing (XfcekbdIndicator * gki, int group);
+static void
+xfcekbd_indicator_set_current_page_for_group (XfcekbdIndicator * gki, int group);
+static void
+xfcekbd_indicator_set_current_page (XfcekbdIndicator * gki);
+static void
+xfcekbd_indicator_cleanup (XfcekbdIndicator * gki);
+static void
+xfcekbd_indicator_fill (XfcekbdIndicator * gki);
+static void
+xfcekbd_indicator_set_tooltips (XfcekbdIndicator * gki, const char *str);
+
+static void
+xfcekbd_indicator_load_images ()
+{
+	int i;
+	GSList *image_filename;
+
+	globals.images = NULL;
+	xfcekbd_indicator_config_load_image_filenames (&globals.ind_cfg,
+						    &globals.kbd_cfg);
+
+	if (!globals.ind_cfg.show_flags)
+		return;
+
+	image_filename = globals.ind_cfg.image_filenames;
+
+	for (i = xkl_engine_get_max_num_groups (globals.engine);
+	     --i >= 0; image_filename = image_filename->next) {
+		GdkPixbuf *image = NULL;
+		char *image_file = (char *) image_filename->data;
+
+		if (image_file != NULL) {
+			GError *gerror = NULL;
+			image =
+			    gdk_pixbuf_new_from_file (image_file, &gerror);
+			if (image == NULL) {
+				GtkWidget *dialog =
+				    gtk_message_dialog_new (NULL,
+							    GTK_DIALOG_DESTROY_WITH_PARENT,
+							    GTK_MESSAGE_ERROR,
+							    GTK_BUTTONS_OK,
+							    _
+							    ("There was an error loading an image: %s"),
+							    gerror->
+							    message);
+				g_signal_connect (G_OBJECT (dialog),
+						  "response",
+						  G_CALLBACK
+						  (gtk_widget_destroy),
+						  NULL);
+
+				gtk_window_set_resizable (GTK_WINDOW
+							  (dialog), FALSE);
+
+				gtk_widget_show (dialog);
+				g_error_free (gerror);
+			}
+			xkl_debug (150,
+				   "Image %d[%s] loaded -> %p[%dx%d]\n",
+				   i, image_file, image,
+				   gdk_pixbuf_get_width (image),
+				   gdk_pixbuf_get_height (image));
+		}
+		/* We append the image anyway - even if it is NULL! */
+		globals.images = g_slist_append (globals.images, image);
+	}
+}
+
+static void
+xfcekbd_indicator_free_images ()
+{
+	GdkPixbuf *pi;
+	GSList *img_node;
+
+	xfcekbd_indicator_config_free_image_filenames (&globals.ind_cfg);
+
+	while ((img_node = globals.images) != NULL) {
+		pi = GDK_PIXBUF (img_node->data);
+		/* It can be NULL - some images may be missing */
+		if (pi != NULL) {
+			g_object_unref (pi);
+		}
+		globals.images =
+		    g_slist_remove_link (globals.images, img_node);
+		g_slist_free_1 (img_node);
+	}
+}
+
+static void
+xfcekbd_indicator_update_images (void)
+{
+	xfcekbd_indicator_free_images ();
+	xfcekbd_indicator_load_images ();
+}
+
+void
+xfcekbd_indicator_set_tooltips (XfcekbdIndicator * gki, const char *str)
+{
+	g_assert (str == NULL || g_utf8_validate (str, -1, NULL));
+
+	gtk_widget_set_tooltip_text (GTK_WIDGET (gki), str);
+
+	if (gki->priv->set_parent_tooltips) {
+		GtkWidget *parent =
+		    gtk_widget_get_parent (GTK_WIDGET (gki));
+		if (parent) {
+			gtk_widget_set_tooltip_text (parent, str);
+		}
+	}
+}
+
+void
+xfcekbd_indicator_cleanup (XfcekbdIndicator * gki)
+{
+	int i;
+	GtkNotebook *notebook = GTK_NOTEBOOK (gki);
+
+	/* Do not remove the first page! It is the default page */
+	for (i = gtk_notebook_get_n_pages (notebook); --i > 0;) {
+		gtk_notebook_remove_page (notebook, i);
+	}
+}
+
+void
+xfcekbd_indicator_fill (XfcekbdIndicator * gki)
+{
+	int grp;
+	int total_groups = xkl_engine_get_num_groups (globals.engine);
+	GtkNotebook *notebook = GTK_NOTEBOOK (gki);
+
+	for (grp = 0; grp < total_groups; grp++) {
+		GtkWidget *page;
+		page = xfcekbd_indicator_prepare_drawing (gki, grp);
+
+		if (page == NULL)
+			page = gtk_label_new ("");
+
+		gtk_notebook_append_page (notebook, page, NULL);
+		gtk_widget_show_all (page);
+	}
+}
+
+static gboolean xfcekbd_indicator_key_pressed(GtkWidget* widget, GdkEventKey* event, XfcekbdIndicator* gki)
+{
+	switch (event->keyval)
+	{
+			case GDK_KEY_KP_Enter:
+			case GDK_KEY_ISO_Enter:
+			case GDK_KEY_3270_Enter:
+			case GDK_KEY_Return:
+			case GDK_KEY_space:
+			case GDK_KEY_KP_Space:
+			xfcekbd_desktop_config_lock_next_group(&globals.cfg);
+			return TRUE;
+		default:
+			break;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+xfcekbd_indicator_button_pressed (GtkWidget *
+			       widget,
+			       GdkEventButton * event, XfcekbdIndicator * gki)
+{
+	GtkWidget *img = gtk_bin_get_child (GTK_BIN (widget));
+	GtkAllocation allocation;
+	gtk_widget_get_allocation (img, &allocation);
+	xkl_debug (150, "Flag img size %d x %d\n",
+		   allocation.width, allocation.height);
+	if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
+		xkl_debug (150, "Mouse button pressed on applet\n");
+		xfcekbd_desktop_config_lock_next_group (&globals.cfg);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static void
+draw_flag (GtkWidget * flag, cairo_t * cr, GdkPixbuf * image)
+{
+	/* Image width and height */
+	int iw = gdk_pixbuf_get_width (image);
+	int ih = gdk_pixbuf_get_height (image);
+	GtkAllocation allocation;
+	double xwiratio, ywiratio, wiratio;
+
+	gtk_widget_get_allocation (flag, &allocation);
+
+	/* widget-to-image scales, X and Y */
+	xwiratio = 1.0 * allocation.width / iw;
+	ywiratio = 1.0 * allocation.height / ih;
+	wiratio = xwiratio < ywiratio ? xwiratio : ywiratio;
+
+	/* transform cairo context */
+	cairo_translate (cr, allocation.width / 2.0, allocation.height / 2.0);
+	cairo_scale (cr, wiratio, wiratio);
+	cairo_translate (cr, - iw / 2.0, - ih / 2.0);
+
+	gdk_cairo_set_source_pixbuf (cr, image, 0, 0);
+	cairo_paint (cr);
+}
+
+static gchar *
+xfcekbd_indicator_extract_layout_name (int group, XklEngine * engine,
+				    XfcekbdKeyboardConfig * kbd_cfg,
+				    gchar ** short_group_names,
+				    gchar ** full_group_names)
+{
+	char *layout_name = NULL;
+	if (group < g_strv_length (short_group_names)) {
+		if (xkl_engine_get_features (engine) &
+		    XKLF_MULTIPLE_LAYOUTS_SUPPORTED) {
+			char *full_layout_name =
+			    kbd_cfg->layouts_variants[group];
+			char *variant_name;
+			if (!xfcekbd_keyboard_config_split_items
+			    (full_layout_name, &layout_name,
+			     &variant_name))
+				/* just in case */
+				layout_name = full_layout_name;
+
+			/* make it freeable */
+			layout_name = g_strdup (layout_name);
+
+			if (short_group_names != NULL) {
+				char *short_group_name =
+				    short_group_names[group];
+				if (short_group_name != NULL
+				    && *short_group_name != '\0') {
+					/* drop the long name */
+					g_free (layout_name);
+					layout_name =
+					    g_strdup (short_group_name);
+				}
+			}
+		} else {
+			layout_name = g_strdup (full_group_names[group]);
+		}
+	}
+
+	if (layout_name == NULL)
+		layout_name = g_strdup ("");
+
+	return layout_name;
+}
+
+static gchar *
+xfcekbd_indicator_create_label_title (int group, GHashTable ** ln2cnt_map,
+				   gchar * layout_name)
+{
+	gpointer pcounter = NULL;
+	char *prev_layout_name = NULL;
+	char *lbl_title = NULL;
+	int counter = 0;
+
+	if (group == 0) {
+		*ln2cnt_map =
+		    g_hash_table_new_full (g_str_hash, g_str_equal,
+					   g_free, NULL);
+	}
+
+	/* Process layouts with repeating description */
+	if (g_hash_table_lookup_extended
+	    (*ln2cnt_map, layout_name, (gpointer *) & prev_layout_name,
+	     &pcounter)) {
+		/* "next" same description */
+		gchar appendix[10] = "";
+		gint utf8length;
+		gunichar cidx;
+		counter = GPOINTER_TO_INT (pcounter);
+		/* Unicode subscript 2, 3, 4 */
+		cidx = 0x2081 + counter;
+		utf8length = g_unichar_to_utf8 (cidx, appendix);
+		appendix[utf8length] = '\0';
+		lbl_title = g_strconcat (layout_name, appendix, NULL);
+	} else {
+		/* "first" time this description */
+		lbl_title = g_strdup (layout_name);
+	}
+	g_hash_table_insert (*ln2cnt_map, layout_name,
+			     GINT_TO_POINTER (counter + 1));
+	return lbl_title;
+}
+
+static GtkWidget *
+xfcekbd_indicator_prepare_drawing (XfcekbdIndicator * gki, int group)
+{
+	gpointer pimage;
+	GdkPixbuf *image;
+	GtkWidget *ebox;
+
+	pimage = g_slist_nth_data (globals.images, group);
+	ebox = gtk_event_box_new ();
+	gtk_event_box_set_visible_window (GTK_EVENT_BOX (ebox), FALSE);
+	if (globals.ind_cfg.show_flags) {
+		GtkWidget *flag;
+		if (pimage == NULL)
+			return NULL;
+		image = GDK_PIXBUF (pimage);
+		flag = gtk_drawing_area_new ();
+		gtk_widget_add_events (GTK_WIDGET (flag),
+				       GDK_BUTTON_PRESS_MASK);
+		g_signal_connect (G_OBJECT (flag), "draw",
+		                  G_CALLBACK (draw_flag), image);
+		gtk_container_add (GTK_CONTAINER (ebox), flag);
+	} else {
+		char *lbl_title = NULL;
+		char *layout_name = NULL;
+		GtkWidget *label;
+		static GHashTable *ln2cnt_map = NULL;
+
+		layout_name =
+		    xfcekbd_indicator_extract_layout_name (group,
+							globals.engine,
+							&globals.kbd_cfg,
+							globals.short_group_names,
+							globals.full_group_names);
+
+		lbl_title =
+		    xfcekbd_indicator_create_label_title (group,
+						       &ln2cnt_map,
+						       layout_name);
+
+		label = gtk_label_new (lbl_title);
+		gtk_widget_set_halign (label, GTK_ALIGN_CENTER);
+		gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
+		gtk_widget_set_margin_start (label, 2);
+		gtk_widget_set_margin_end (label, 2);
+		gtk_widget_set_margin_top (label, 2);
+		gtk_widget_set_margin_bottom (label, 2);
+		g_free (lbl_title);
+		gtk_label_set_angle (GTK_LABEL (label), gki->priv->angle);
+
+		if (group + 1 ==
+		    xkl_engine_get_num_groups (globals.engine)) {
+			g_hash_table_destroy (ln2cnt_map);
+			ln2cnt_map = NULL;
+		}
+
+		gtk_container_add (GTK_CONTAINER (ebox), label);
+	}
+
+	g_signal_connect (G_OBJECT (ebox),
+			  "button_press_event",
+			  G_CALLBACK (xfcekbd_indicator_button_pressed), gki);
+
+	g_signal_connect (G_OBJECT (gki),
+			  "key_press_event",
+			  G_CALLBACK (xfcekbd_indicator_key_pressed), gki);
+
+	/* We have everything prepared for that size */
+
+	return ebox;
+}
+
+static void
+xfcekbd_indicator_update_tooltips (XfcekbdIndicator * gki)
+{
+	XklState *state = xkl_engine_get_current_state (globals.engine);
+	gchar *buf;
+	if (state == NULL || state->group < 0
+	    || state->group >= g_strv_length (globals.full_group_names))
+		return;
+
+	buf = g_strdup_printf (globals.tooltips_format,
+			       globals.full_group_names[state->group]);
+
+	xfcekbd_indicator_set_tooltips (gki, buf);
+	g_free (buf);
+}
+
+static void
+xfcekbd_indicator_parent_set (GtkWidget * gki, GtkWidget * previous_parent)
+{
+	xfcekbd_indicator_update_tooltips (XFCEKBD_INDICATOR (gki));
+}
+
+
+void
+xfcekbd_indicator_reinit_ui (XfcekbdIndicator * gki)
+{
+	xfcekbd_indicator_cleanup (gki);
+	xfcekbd_indicator_fill (gki);
+
+	xfcekbd_indicator_set_current_page (gki);
+
+	g_signal_emit_by_name (gki, "reinit-ui");
+}
+
+/* Should be called once for all widgets */
+static void
+xfcekbd_indicator_cfg_changed (GSettings *settings,
+			       gchar     *key,
+			       gpointer   user_data)
+{
+	xkl_debug (100,
+		   "General configuration changed in GSettings - reiniting...\n");
+	xfcekbd_desktop_config_load_from_gsettings (&globals.cfg);
+	xfcekbd_desktop_config_activate (&globals.cfg);
+	ForAllIndicators () {
+		xfcekbd_indicator_reinit_ui (gki);
+	} NextIndicator ();
+}
+
+/* Should be called once for all widgets */
+static void
+xfcekbd_indicator_ind_cfg_changed (GSettings *settings,
+				   gchar     *key,
+				   gpointer   user_data)
+{
+	xkl_debug (100,
+		   "Applet configuration changed in GSettings - reiniting...\n");
+	xfcekbd_indicator_config_load_from_gsettings (&globals.ind_cfg);
+	xfcekbd_indicator_update_images ();
+	xfcekbd_indicator_config_activate (&globals.ind_cfg);
+
+	ForAllIndicators () {
+		xfcekbd_indicator_reinit_ui (gki);
+	} NextIndicator ();
+}
+
+static void
+xfcekbd_indicator_load_group_names (const gchar ** layout_ids,
+				 const gchar ** variant_ids)
+{
+	if (!xfcekbd_desktop_config_load_group_descriptions
+	    (&globals.cfg, globals.registry, layout_ids, variant_ids,
+	     &globals.short_group_names, &globals.full_group_names)) {
+		/* We just populate no short names (remain NULL) -
+		 * full names are going to be used anyway */
+		gint i, total_groups =
+		    xkl_engine_get_num_groups (globals.engine);
+		globals.full_group_names =
+		    g_new0 (gchar *, total_groups + 1);
+
+		if (xkl_engine_get_features (globals.engine) &
+		    XKLF_MULTIPLE_LAYOUTS_SUPPORTED) {
+			gchar **lst = globals.kbd_cfg.layouts_variants;
+			for (i = 0; *lst; lst++, i++) {
+				globals.full_group_names[i] =
+				    g_strdup ((char *) *lst);
+			}
+		} else {
+			for (i = total_groups; --i >= 0;) {
+				globals.full_group_names[i] =
+				    g_strdup_printf ("Group %d", i);
+			}
+		}
+	}
+}
+
+/* Should be called once for all widgets */
+static void
+xfcekbd_indicator_kbd_cfg_callback (XfcekbdIndicator * gki)
+{
+	XklConfigRec *xklrec = xkl_config_rec_new ();
+	xkl_debug (100,
+		   "XKB configuration changed on X Server - reiniting...\n");
+
+	xfcekbd_keyboard_config_load_from_x_current (&globals.kbd_cfg,
+						  xklrec);
+	xfcekbd_indicator_update_images ();
+
+	g_strfreev (globals.full_group_names);
+	globals.full_group_names = NULL;
+
+	if (globals.short_group_names != NULL) {
+		g_strfreev (globals.short_group_names);
+		globals.short_group_names = NULL;
+	}
+
+	xfcekbd_indicator_load_group_names ((const gchar **) xklrec->layouts,
+					 (const gchar **)
+					 xklrec->variants);
+
+	ForAllIndicators () {
+		xfcekbd_indicator_reinit_ui (gki);
+	} NextIndicator ();
+	g_object_unref (G_OBJECT (xklrec));
+}
+
+/* Should be called once for all applets */
+static void
+xfcekbd_indicator_state_callback (XklEngine * engine,
+			       XklEngineStateChange changeType,
+			       gint group, gboolean restore)
+{
+	xkl_debug (150, "group is now %d, restore: %d\n", group, restore);
+
+	if (changeType == GROUP_CHANGED) {
+		ForAllIndicators () {
+			xkl_debug (200, "do repaint\n");
+			xfcekbd_indicator_set_current_page_for_group
+			    (gki, group);
+		}
+		NextIndicator ();
+	}
+}
+
+
+void
+xfcekbd_indicator_set_current_page (XfcekbdIndicator * gki)
+{
+	XklState *cur_state;
+	cur_state = xkl_engine_get_current_state (globals.engine);
+	if (cur_state->group >= 0)
+		xfcekbd_indicator_set_current_page_for_group (gki,
+							   cur_state->
+							   group);
+}
+
+void
+xfcekbd_indicator_set_current_page_for_group (XfcekbdIndicator * gki, int group)
+{
+	xkl_debug (200, "Revalidating for group %d\n", group);
+
+	gtk_notebook_set_current_page (GTK_NOTEBOOK (gki), group + 1);
+
+	xfcekbd_indicator_update_tooltips (gki);
+}
+
+/* Should be called once for all widgets */
+static GdkFilterReturn
+xfcekbd_indicator_filter_x_evt (GdkXEvent * xev, GdkEvent * event)
+{
+	XEvent *xevent = (XEvent *) xev;
+
+	xkl_engine_filter_events (globals.engine, xevent);
+	switch (xevent->type) {
+	case ReparentNotify:
+		{
+			XReparentEvent *rne = (XReparentEvent *) xev;
+
+			ForAllIndicators () {
+				GdkWindow *w =
+				    gtk_widget_get_parent_window
+				    (GTK_WIDGET (gki));
+
+				/* compare the indicator's parent window with the even window */
+				if (w != NULL
+				    && GDK_WINDOW_XID (w) == rne->window) {
+					/* if so - make it transparent... */
+					xkl_engine_set_window_transparent
+					    (globals.engine, rne->window,
+					     TRUE);
+				}
+			}
+			NextIndicator ()
+		}
+		break;
+	}
+	return GDK_FILTER_CONTINUE;
+}
+
+
+/* Should be called once for all widgets */
+static void
+xfcekbd_indicator_start_listen (void)
+{
+	gdk_window_add_filter (NULL, (GdkFilterFunc)
+			       xfcekbd_indicator_filter_x_evt, NULL);
+	gdk_window_add_filter (gdk_get_default_root_window (),
+			       (GdkFilterFunc)
+			       xfcekbd_indicator_filter_x_evt, NULL);
+
+	xkl_engine_start_listen (globals.engine,
+				 XKLL_TRACK_KEYBOARD_STATE);
+}
+
+/* Should be called once for all widgets */
+static void
+xfcekbd_indicator_stop_listen (void)
+{
+	xkl_engine_stop_listen (globals.engine, XKLL_TRACK_KEYBOARD_STATE);
+
+	gdk_window_remove_filter (NULL, (GdkFilterFunc)
+				  xfcekbd_indicator_filter_x_evt, NULL);
+	gdk_window_remove_filter
+	    (gdk_get_default_root_window (),
+	     (GdkFilterFunc) xfcekbd_indicator_filter_x_evt, NULL);
+}
+
+static gboolean
+xfcekbd_indicator_scroll (GtkWidget * gki, GdkEventScroll * event)
+{
+	/* mouse wheel events should be ignored, otherwise funny effects appear */
+	return TRUE;
+}
+
+static void xfcekbd_indicator_init(XfcekbdIndicator* gki)
+{
+	GtkWidget *def_drawing;
+	GtkNotebook *notebook;
+
+	if (!g_slist_length(globals.widget_instances))
+	{
+		xfcekbd_indicator_global_init();
+	}
+
+	gki->priv = g_new0 (XfcekbdIndicatorPrivate, 1);
+
+	notebook = GTK_NOTEBOOK (gki);
+
+	xkl_debug (100, "Initiating the widget startup process for %p\n", gki);
+
+	gtk_notebook_set_show_tabs (notebook, FALSE);
+	gtk_notebook_set_show_border (notebook, FALSE);
+
+	def_drawing =
+	    gtk_image_new_from_icon_name ("process-stop",
+				      GTK_ICON_SIZE_BUTTON);
+
+	gtk_notebook_append_page (notebook, def_drawing,
+				  gtk_label_new (""));
+
+	if (globals.engine == NULL) {
+		xfcekbd_indicator_set_tooltips (gki,
+					     _
+					     ("XKB initialization error"));
+		return;
+	}
+
+	xfcekbd_indicator_set_tooltips (gki, NULL);
+
+	xfcekbd_indicator_fill (gki);
+	xfcekbd_indicator_set_current_page (gki);
+
+	gtk_widget_add_events (GTK_WIDGET (gki), GDK_BUTTON_PRESS_MASK);
+
+	/* append AFTER all initialization work is finished */
+	globals.widget_instances =
+	    g_slist_append (globals.widget_instances, gki);
+}
+
+static void
+xfcekbd_indicator_finalize (GObject * obj)
+{
+	XfcekbdIndicator *gki = XFCEKBD_INDICATOR (obj);
+	xkl_debug (100,
+		   "Starting the mate-kbd-indicator widget shutdown process for %p\n",
+		   gki);
+
+	/* remove BEFORE all termination work is finished */
+	globals.widget_instances =
+	    g_slist_remove (globals.widget_instances, gki);
+
+	xfcekbd_indicator_cleanup (gki);
+
+	xkl_debug (100,
+		   "The instance of mate-kbd-indicator successfully finalized\n");
+
+	g_free (gki->priv);
+
+	G_OBJECT_CLASS (xfcekbd_indicator_parent_class)->finalize (obj);
+
+	if (!g_slist_length (globals.widget_instances))
+		xfcekbd_indicator_global_term ();
+}
+
+static void
+xfcekbd_indicator_global_term (void)
+{
+	xkl_debug (100, "*** Last  XfcekbdIndicator instance *** \n");
+	xfcekbd_indicator_stop_listen ();
+
+	xfcekbd_desktop_config_stop_listen (&globals.cfg);
+	xfcekbd_indicator_config_stop_listen (&globals.ind_cfg);
+
+	xfcekbd_indicator_config_term (&globals.ind_cfg);
+	xfcekbd_keyboard_config_term (&globals.kbd_cfg);
+	xfcekbd_desktop_config_term (&globals.cfg);
+
+	g_object_unref (G_OBJECT (globals.registry));
+	globals.registry = NULL;
+	g_object_unref (G_OBJECT (globals.engine));
+	globals.engine = NULL;
+	xkl_debug (100, "*** Terminated globals *** \n");
+}
+
+static void
+xfcekbd_indicator_class_init (XfcekbdIndicatorClass * klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+	xkl_debug (100, "*** First XfcekbdIndicator instance *** \n");
+
+	memset (&globals, 0, sizeof (globals));
+
+	/* Initing some global vars */
+	globals.tooltips_format = "%s";
+
+	/* Initing vtable */
+	object_class->finalize = xfcekbd_indicator_finalize;
+
+	widget_class->scroll_event = xfcekbd_indicator_scroll;
+	widget_class->parent_set = xfcekbd_indicator_parent_set;
+
+	/* Signals */
+	g_signal_new ("reinit-ui", XFCEKBD_TYPE_INDICATOR,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (XfcekbdIndicatorClass, reinit_ui),
+		      NULL, NULL, xfcekbd_indicator_VOID__VOID,
+		      G_TYPE_NONE, 0);
+}
+
+static void
+xfcekbd_indicator_global_init (void)
+{
+	XklConfigRec *xklrec = xkl_config_rec_new ();
+
+	globals.engine = xkl_engine_get_instance(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()));
+
+	if (globals.engine == NULL)
+	{
+		xkl_debug (0, "Libxklavier initialization error");
+		return;
+	}
+
+	g_signal_connect (globals.engine, "X-state-changed",
+			  G_CALLBACK (xfcekbd_indicator_state_callback),
+			  NULL);
+	g_signal_connect (globals.engine, "X-config-changed",
+			  G_CALLBACK (xfcekbd_indicator_kbd_cfg_callback),
+			  NULL);
+
+	xfcekbd_desktop_config_init (&globals.cfg, globals.engine);
+	xfcekbd_keyboard_config_init (&globals.kbd_cfg, globals.engine);
+	xfcekbd_indicator_config_init (&globals.ind_cfg, globals.engine);
+
+	xfcekbd_desktop_config_start_listen (&globals.cfg,
+					  (GCallback)
+					  xfcekbd_indicator_cfg_changed,
+					  NULL);
+	xfcekbd_indicator_config_start_listen (&globals.ind_cfg,
+					    (GCallback)
+					    xfcekbd_indicator_ind_cfg_changed,
+					    NULL);
+
+	xfcekbd_desktop_config_load_from_gsettings (&globals.cfg);
+	xfcekbd_desktop_config_activate (&globals.cfg);
+
+	globals.registry =
+	    xkl_config_registry_get_instance (globals.engine);
+	xkl_config_registry_load (globals.registry,
+				  globals.cfg.load_extra_items);
+
+	xfcekbd_keyboard_config_load_from_x_current (&globals.kbd_cfg,
+						  xklrec);
+
+	xfcekbd_indicator_config_load_from_gsettings (&globals.ind_cfg);
+	xfcekbd_indicator_update_images ();
+	xfcekbd_indicator_config_activate (&globals.ind_cfg);
+
+	xfcekbd_indicator_load_group_names ((const gchar **) xklrec->layouts,
+					 (const gchar **)
+					 xklrec->variants);
+	g_object_unref (G_OBJECT (xklrec));
+
+	xfcekbd_indicator_start_listen ();
+
+	xkl_debug (100, "*** Inited globals *** \n");
+}
+
+GtkWidget *
+xfcekbd_indicator_new (void)
+{
+	return
+	    GTK_WIDGET (g_object_new (xfcekbd_indicator_get_type (), NULL));
+}
+
+void
+xfcekbd_indicator_set_parent_tooltips (XfcekbdIndicator * gki, gboolean spt)
+{
+	gki->priv->set_parent_tooltips = spt;
+	xfcekbd_indicator_update_tooltips (gki);
+}
+
+void
+xfcekbd_indicator_set_tooltips_format (const gchar format[])
+{
+	globals.tooltips_format = format;
+	ForAllIndicators ()
+	    xfcekbd_indicator_update_tooltips (gki);
+	NextIndicator ()
+}
+
+/**
+ * xfcekbd_indicator_get_xkl_engine:
+ *
+ * Returns: (transfer none): The engine shared by all XfcekbdIndicator objects
+ */
+XklEngine *
+xfcekbd_indicator_get_xkl_engine ()
+{
+	return globals.engine;
+}
+
+/**
+ * xfcekbd_indicator_get_group_names:
+ *
+ * Returns: (transfer none) (array zero-terminated=1): List of group names
+ */
+gchar **
+xfcekbd_indicator_get_group_names ()
+{
+	return globals.full_group_names;
+}
+
+gchar *
+xfcekbd_indicator_get_image_filename (guint group)
+{
+	if (!globals.ind_cfg.show_flags)
+		return NULL;
+	return xfcekbd_indicator_config_get_images_file (&globals.ind_cfg,
+						      &globals.kbd_cfg,
+						      group);
+}
+
+gdouble
+xfcekbd_indicator_get_max_width_height_ratio (void)
+{
+	gdouble rv = 0.0;
+	GSList *ip = globals.images;
+	if (!globals.ind_cfg.show_flags)
+		return 0;
+	while (ip != NULL) {
+		GdkPixbuf *img = GDK_PIXBUF (ip->data);
+		gdouble r =
+		    1.0 * gdk_pixbuf_get_width (img) /
+		    gdk_pixbuf_get_height (img);
+		if (r > rv)
+			rv = r;
+		ip = ip->next;
+	}
+	return rv;
+}
+
+void
+xfcekbd_indicator_set_angle (XfcekbdIndicator * gki, gdouble angle)
+{
+	gki->priv->angle = angle;
+}
diff --git a/src/xfcekbd-indicator.h b/src/xfcekbd-indicator.h
new file mode 100644
index 0000000..f710ad7
--- /dev/null
+++ b/src/xfcekbd-indicator.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2006 Sergey V. Udaltsov <svu at gnome.org>
+ *
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __XFCEKBD_INDICATOR_H__
+#define __XFCEKBD_INDICATOR_H__
+
+#include <gtk/gtk.h>
+
+#include <libxklavier/xklavier.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+	typedef struct _XfcekbdIndicator XfcekbdIndicator;
+	typedef struct _XfcekbdIndicatorPrivate XfcekbdIndicatorPrivate;
+	typedef struct _XfcekbdIndicatorClass XfcekbdIndicatorClass;
+
+#define XFCEKBD_TYPE_INDICATOR             (xfcekbd_indicator_get_type ())
+#define XFCEKBD_INDICATOR(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), XFCEKBD_TYPE_INDICATOR, XfcekbdIndicator))
+#define XFCEKBD_INDICATOR_CLASS(obj)       (G_TYPE_CHECK_CLASS_CAST ((obj), XFCEKBD_TYPE_INDICATOR,  XfcekbdIndicatorClass))
+#define XFCEKBD_IS_INDICATOR(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), XFCEKBD_TYPE_INDICATOR))
+#define XFCEKBD_IS_INDICATOR_CLASS(obj)    (G_TYPE_CHECK_CLASS_TYPE ((obj), XFCEKBD_TYPE_INDICATOR))
+#define XFCEKBD_INDICATOR_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), XFCEKBD_TYPE_INDICATOR, XfcekbdIndicatorClass))
+
+	struct _XfcekbdIndicator {
+		GtkNotebook parent;
+		XfcekbdIndicatorPrivate *priv;
+	};
+
+	struct _XfcekbdIndicatorClass {
+		GtkNotebookClass parent_class;
+
+		void (*reinit_ui) (XfcekbdIndicator * gki);
+	};
+
+	extern GType xfcekbd_indicator_get_type (void);
+
+	extern GtkWidget *xfcekbd_indicator_new (void);
+
+	extern void xfcekbd_indicator_reinit_ui (XfcekbdIndicator * gki);
+
+	extern void xfcekbd_indicator_set_angle (XfcekbdIndicator * gki,
+					      gdouble angle);
+
+	extern XklEngine *xfcekbd_indicator_get_xkl_engine (void);
+
+	extern gchar **xfcekbd_indicator_get_group_names (void);
+
+	extern gchar *xfcekbd_indicator_get_image_filename (guint group);
+
+	extern gdouble xfcekbd_indicator_get_max_width_height_ratio (void);
+
+	extern void
+	 xfcekbd_indicator_set_parent_tooltips (XfcekbdIndicator *
+					     gki, gboolean ifset);
+
+	extern void
+	 xfcekbd_indicator_set_tooltips_format (const gchar str[]);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/xfcekbd-keyboard-config.c b/src/xfcekbd-keyboard-config.c
new file mode 100644
index 0000000..bf5d091
--- /dev/null
+++ b/src/xfcekbd-keyboard-config.c
@@ -0,0 +1,796 @@
+/*
+ * Copyright (C) 2006 Sergey V. Udaltsov <svu at gnome.org>
+ *
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <X11/keysym.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "xfcekbd-keyboard-config.h"
+#include "xfcekbd-config-private.h"
+#include "xfcekbd-util.h"
+
+/*
+ * XfcekbdKeyboardConfig
+ */
+#define XFCEKBD_KEYBOARD_CONFIG_SCHEMA XFCEKBD_CONFIG_SCHEMA ".kbd"
+
+#define GROUP_SWITCHERS_GROUP "grp"
+#define DEFAULT_GROUP_SWITCH "grp:shift_caps_toggle"
+
+const gchar XFCEKBD_KEYBOARD_CONFIG_KEY_MODEL[] = "model";
+const gchar XFCEKBD_KEYBOARD_CONFIG_KEY_LAYOUTS[] = "layouts";
+const gchar XFCEKBD_KEYBOARD_CONFIG_KEY_OPTIONS[] = "options";
+
+const gchar *XFCEKBD_KEYBOARD_CONFIG_ACTIVE[] = {
+	XFCEKBD_KEYBOARD_CONFIG_KEY_MODEL,
+	XFCEKBD_KEYBOARD_CONFIG_KEY_LAYOUTS,
+	XFCEKBD_KEYBOARD_CONFIG_KEY_OPTIONS
+};
+
+/*
+ * static common functions
+ */
+
+static gboolean
+g_strv_equal (gchar ** l1, gchar ** l2)
+{
+	if (l1 == l2)
+		return TRUE;
+	if (l1 == NULL)
+		return g_strv_length (l2) == 0;
+	if (l2 == NULL)
+		return g_strv_length (l1) == 0;
+
+	while ((*l1 != NULL) && (*l2 != NULL)) {
+		if (*l1 != *l2) {
+			if (*l1 && *l2) {
+				if (g_ascii_strcasecmp (*l1, *l2))
+					return FALSE;
+			} else
+				return FALSE;
+		}
+
+		l1++;
+		l2++;
+	}
+	return (*l1 == NULL) && (*l2 == NULL);
+}
+
+gboolean
+xfcekbd_keyboard_config_get_lv_descriptions (XklConfigRegistry *
+					  config_registry,
+					  const gchar * layout_name,
+					  const gchar * variant_name,
+					  gchar ** layout_short_descr,
+					  gchar ** layout_descr,
+					  gchar ** variant_short_descr,
+					  gchar ** variant_descr)
+{
+	/* TODO make it not static */
+	static XklConfigItem *litem = NULL;
+	static XklConfigItem *vitem = NULL;
+
+	if (litem == NULL)
+		litem = xkl_config_item_new ();
+	if (vitem == NULL)
+		vitem = xkl_config_item_new ();
+
+	layout_name = g_strdup (layout_name);
+
+	g_snprintf (litem->name, sizeof litem->name, "%s", layout_name);
+	if (xkl_config_registry_find_layout (config_registry, litem)) {
+		*layout_short_descr = litem->short_description;
+		*layout_descr = litem->description;
+	} else
+		*layout_short_descr = *layout_descr = NULL;
+
+	if (variant_name != NULL) {
+		variant_name = g_strdup (variant_name);
+		g_snprintf (vitem->name, sizeof vitem->name, "%s",
+			    variant_name);
+		if (xkl_config_registry_find_variant
+		    (config_registry, layout_name, vitem)) {
+			*variant_short_descr = vitem->short_description;
+			*variant_descr = vitem->description;
+		} else
+			*variant_short_descr = *variant_descr = NULL;
+
+		g_free ((char *) variant_name);
+	} else
+		*variant_descr = NULL;
+
+	g_free ((char *) layout_name);
+	return *layout_descr != NULL;
+}
+
+/*
+ * extern common functions
+ */
+const gchar *
+xfcekbd_keyboard_config_merge_items (const gchar * parent,
+				  const gchar * child)
+{
+	static gchar buffer[XKL_MAX_CI_NAME_LENGTH * 2 - 1];
+	*buffer = '\0';
+	if (parent != NULL) {
+		if (strlen (parent) >= XKL_MAX_CI_NAME_LENGTH)
+			return NULL;
+		strcat (buffer, parent);
+	}
+	if (child != NULL && *child != 0) {
+		if (strlen (child) >= XKL_MAX_CI_NAME_LENGTH)
+			return NULL;
+		strcat (buffer, "\t");
+		strcat (buffer, child);
+	}
+	return buffer;
+}
+
+gboolean
+xfcekbd_keyboard_config_split_items (const gchar * merged, gchar ** parent,
+				  gchar ** child)
+{
+	static gchar pbuffer[XKL_MAX_CI_NAME_LENGTH];
+	static gchar cbuffer[XKL_MAX_CI_NAME_LENGTH];
+	int plen, clen;
+	const gchar *pos;
+	*parent = *child = NULL;
+
+	if (merged == NULL)
+		return FALSE;
+
+	pos = strchr (merged, '\t');
+	if (pos == NULL) {
+		plen = strlen (merged);
+		clen = 0;
+	} else {
+		plen = pos - merged;
+		clen = strlen (pos + 1);
+		if (clen >= XKL_MAX_CI_NAME_LENGTH)
+			return FALSE;
+		strcpy (*child = cbuffer, pos + 1);
+	}
+	if (plen >= XKL_MAX_CI_NAME_LENGTH)
+		return FALSE;
+	memcpy (*parent = pbuffer, merged, plen);
+	pbuffer[plen] = '\0';
+	return TRUE;
+}
+
+/*
+ * static XfcekbdKeyboardConfig functions
+ */
+static void
+xfcekbd_keyboard_config_copy_from_xkl_config (XfcekbdKeyboardConfig * kbd_config,
+					   XklConfigRec * pdata)
+{
+	char **p, **p1;
+	int i;
+	xfcekbd_keyboard_config_model_set (kbd_config, pdata->model);
+	xkl_debug (150, "Loaded Kbd model: [%s]\n", pdata->model);
+
+	/* Layouts */
+	g_strfreev (kbd_config->layouts_variants);
+	kbd_config->layouts_variants = NULL;
+	if (pdata->layouts != NULL) {
+		p = pdata->layouts;
+		p1 = pdata->variants;
+		kbd_config->layouts_variants =
+		    g_new0 (gchar *, g_strv_length (pdata->layouts) + 1);
+		i = 0;
+		while (*p != NULL) {
+			const gchar *full_layout =
+			    xfcekbd_keyboard_config_merge_items (*p, *p1);
+			xkl_debug (150,
+				   "Loaded Kbd layout (with variant): [%s]\n",
+				   full_layout);
+			kbd_config->layouts_variants[i++] =
+			    g_strdup (full_layout);
+			p++;
+			p1++;
+		}
+	}
+
+	/* Options */
+	g_strfreev (kbd_config->options);
+	kbd_config->options = NULL;
+
+	if (pdata->options != NULL) {
+		p = pdata->options;
+		kbd_config->options =
+		    g_new0 (gchar *, g_strv_length (pdata->options) + 1);
+		i = 0;
+		while (*p != NULL) {
+			char group[XKL_MAX_CI_NAME_LENGTH];
+			char *option = *p;
+			char *delim =
+			    (option != NULL) ? strchr (option, ':') : NULL;
+			int len;
+			if ((delim != NULL) &&
+			    ((len =
+			      (delim - option)) <
+			     XKL_MAX_CI_NAME_LENGTH)) {
+				strncpy (group, option, len);
+				group[len] = 0;
+				xkl_debug (150,
+					   "Loaded Kbd option: [%s][%s]\n",
+					   group, option);
+				xfcekbd_keyboard_config_options_set
+				    (kbd_config, i++, group, option);
+			}
+			p++;
+		}
+	}
+}
+
+static void
+xfcekbd_keyboard_config_copy_to_xkl_config (XfcekbdKeyboardConfig * kbd_config,
+					 XklConfigRec * pdata)
+{
+	int i;
+	int num_layouts, num_options;
+	pdata->model =
+	    (kbd_config->model ==
+	     NULL) ? NULL : g_strdup (kbd_config->model);
+
+	num_layouts =
+	    (kbd_config->layouts_variants ==
+	     NULL) ? 0 : g_strv_length (kbd_config->layouts_variants);
+	num_options =
+	    (kbd_config->options ==
+	     NULL) ? 0 : g_strv_length (kbd_config->options);
+
+	xkl_debug (150, "Taking %d layouts\n", num_layouts);
+	if (num_layouts != 0) {
+		gchar **the_layout_variant = kbd_config->layouts_variants;
+		char **p1 = pdata->layouts =
+		    g_new0 (char *, num_layouts + 1);
+		char **p2 = pdata->variants =
+		    g_new0 (char *, num_layouts + 1);
+		for (i = num_layouts; --i >= 0;) {
+			char *layout, *variant;
+			if (xfcekbd_keyboard_config_split_items
+			    (*the_layout_variant, &layout, &variant)
+			    && variant != NULL) {
+				*p1 =
+				    (layout ==
+				     NULL) ? g_strdup ("") :
+				    g_strdup (layout);
+				*p2 =
+				    (variant ==
+				     NULL) ? g_strdup ("") :
+				    g_strdup (variant);
+			} else {
+				*p1 =
+				    (*the_layout_variant ==
+				     NULL) ? g_strdup ("") :
+				    g_strdup (*the_layout_variant);
+				*p2 = g_strdup ("");
+			}
+			xkl_debug (150, "Adding [%s]/%p and [%s]/%p\n",
+				   *p1 ? *p1 : "(nil)", *p1,
+				   *p2 ? *p2 : "(nil)", *p2);
+			p1++;
+			p2++;
+			the_layout_variant++;
+		}
+	}
+
+	if (num_options != 0) {
+		gchar **the_option = kbd_config->options;
+		char **p = pdata->options =
+		    g_new0 (char *, num_options + 1);
+		for (i = num_options; --i >= 0;) {
+			char *group, *option;
+			if (xfcekbd_keyboard_config_split_items
+			    (*the_option, &group, &option)
+			    && option != NULL)
+				*(p++) = g_strdup (option);
+			else {
+				*(p++) = g_strdup ("");
+				xkl_debug (150, "Could not split [%s]\n",
+					   *the_option);
+			}
+			the_option++;
+		}
+	}
+}
+
+static void
+xfcekbd_keyboard_config_load_params (XfcekbdKeyboardConfig * kbd_config,
+				  const gchar * param_names[])
+{
+	gchar *pc;
+
+	pc = g_settings_get_string (kbd_config->settings, param_names[0]);
+	if (pc == NULL) {
+		xfcekbd_keyboard_config_model_set (kbd_config, NULL);
+	} else {
+		xfcekbd_keyboard_config_model_set (kbd_config, pc);
+		g_free (pc);
+	}
+	xkl_debug (150, "Loaded Kbd model: [%s]\n",
+		   kbd_config->model ? kbd_config->model : "(null)");
+
+	g_strfreev (kbd_config->layouts_variants);
+
+	kbd_config->layouts_variants =
+	    g_settings_get_strv (kbd_config->settings, param_names[1]);
+
+	if (kbd_config->layouts_variants != NULL
+	    && kbd_config->layouts_variants[0] == NULL) {
+		g_strfreev (kbd_config->layouts_variants);
+		kbd_config->layouts_variants = NULL;
+	}
+
+	g_strfreev (kbd_config->options);
+
+	kbd_config->options =
+	    g_settings_get_strv (kbd_config->settings, param_names[2]);
+
+	if (kbd_config->options != NULL && kbd_config->options[0] == NULL) {
+		g_strfreev (kbd_config->options);
+		kbd_config->options = NULL;
+	}
+}
+
+static void
+xfcekbd_keyboard_config_save_params (XfcekbdKeyboardConfig * kbd_config,
+				  const gchar * param_names[])
+{
+	gchar **pl;
+
+	if (kbd_config->model)
+		g_settings_set_string (kbd_config->settings, param_names[0],
+				       kbd_config->model);
+	else
+		g_settings_set_string (kbd_config->settings, param_names[0],
+				       NULL);
+	xkl_debug (150, "Saved Kbd model: [%s]\n",
+		   kbd_config->model ? kbd_config->model : "(null)");
+
+	if (kbd_config->layouts_variants) {
+		pl = kbd_config->layouts_variants;
+		while (*pl != NULL) {
+			xkl_debug (150, "Saved Kbd layout: [%s]\n", *pl);
+			pl++;
+		}
+		g_settings_set_strv (kbd_config->settings,
+				     param_names[1],
+				     (const gchar * const *)
+				     kbd_config->layouts_variants);
+	} else {
+		xkl_debug (150, "Saved Kbd layouts: []\n");
+		g_settings_set_strv (kbd_config->settings,
+				     param_names[1], NULL);
+	}
+
+	if (kbd_config->options) {
+		pl = kbd_config->options;
+		while (*pl != NULL) {
+			xkl_debug (150, "Saved Kbd option: [%s]\n", *pl);
+			pl++;
+		}
+		g_settings_set_strv (kbd_config->settings,
+				     param_names[2],
+				     (const gchar *
+				      const *) kbd_config->options);
+	} else {
+		xkl_debug (150, "Saved Kbd options: []\n");
+		g_settings_set_strv (kbd_config->settings,
+				     param_names[2], NULL);
+	}
+}
+
+/*
+ * extern XfcekbdKeyboardConfig config functions
+ */
+void
+xfcekbd_keyboard_config_init (XfcekbdKeyboardConfig * kbd_config,
+			      XklEngine * engine)
+{
+	memset (kbd_config, 0, sizeof (*kbd_config));
+	kbd_config->settings = g_settings_new (XFCEKBD_KEYBOARD_CONFIG_SCHEMA);
+	kbd_config->engine = engine;
+}
+
+void
+xfcekbd_keyboard_config_term (XfcekbdKeyboardConfig * kbd_config)
+{
+	xfcekbd_keyboard_config_model_set (kbd_config, NULL);
+
+	g_strfreev (kbd_config->layouts_variants);
+	kbd_config->layouts_variants = NULL;
+	g_strfreev (kbd_config->options);
+	kbd_config->options = NULL;
+
+	g_object_unref (kbd_config->settings);
+	kbd_config->settings = NULL;
+}
+
+void
+xfcekbd_keyboard_config_load_from_gsettings (XfcekbdKeyboardConfig * kbd_config,
+				      XfcekbdKeyboardConfig *
+				      kbd_config_default)
+{
+	xfcekbd_keyboard_config_load_params (kbd_config,
+					  XFCEKBD_KEYBOARD_CONFIG_ACTIVE);
+
+	if (kbd_config_default != NULL) {
+
+		if (kbd_config->model == NULL)
+			kbd_config->model =
+			    g_strdup (kbd_config_default->model);
+
+		if (kbd_config->layouts_variants == NULL) {
+			kbd_config->layouts_variants =
+			    g_strdupv
+			    (kbd_config_default->layouts_variants);
+		}
+
+		if (kbd_config->options == NULL) {
+			kbd_config->options =
+			    g_strdupv (kbd_config_default->options);
+		}
+	}
+}
+
+void
+xfcekbd_keyboard_config_load_from_x_current (XfcekbdKeyboardConfig * kbd_config,
+					  XklConfigRec * data)
+{
+	gboolean own_data = data == NULL;
+	xkl_debug (150, "Copying config from X(current)\n");
+	if (own_data)
+		data = xkl_config_rec_new ();
+	if (xkl_config_rec_get_from_server (data, kbd_config->engine))
+		xfcekbd_keyboard_config_copy_from_xkl_config (kbd_config,
+							   data);
+	else
+		xkl_debug (150,
+			   "Could not load keyboard config from server: [%s]\n",
+			   xkl_get_last_error ());
+	if (own_data)
+		g_object_unref (G_OBJECT (data));
+}
+
+void
+xfcekbd_keyboard_config_load_from_x_initial (XfcekbdKeyboardConfig * kbd_config,
+					  XklConfigRec * data)
+{
+	gboolean own_data = data == NULL;
+	xkl_debug (150, "Copying config from X(initial)\n");
+	if (own_data)
+		data = xkl_config_rec_new ();
+	if (xkl_config_rec_get_from_backup (data, kbd_config->engine))
+		xfcekbd_keyboard_config_copy_from_xkl_config (kbd_config,
+							   data);
+	else
+		xkl_debug (150,
+			   "Could not load keyboard config from backup: [%s]\n",
+			   xkl_get_last_error ());
+	if (own_data)
+		g_object_unref (G_OBJECT (data));
+}
+
+static gboolean
+xfcekbd_keyboard_config_options_equals (XfcekbdKeyboardConfig * kbd_config1,
+				     XfcekbdKeyboardConfig * kbd_config2)
+{
+	int num_options, num_options2;
+
+	num_options =
+	    (kbd_config1->options ==
+	     NULL) ? 0 : g_strv_length (kbd_config1->options);
+	num_options2 =
+	    (kbd_config2->options ==
+	     NULL) ? 0 : g_strv_length (kbd_config2->options);
+
+	if (num_options != num_options2)
+		return False;
+
+	if (num_options != 0) {
+		int i;
+		char *group1, *option1;
+
+		for (i = 0; i < num_options; i++) {
+			int j;
+			char *group2, *option2;
+			gboolean are_equal = FALSE;
+
+			if (!xfcekbd_keyboard_config_split_items
+			    (kbd_config1->options[i], &group1, &option1))
+				continue;
+
+			option1 = g_strdup (option1);
+
+			for (j = 0; j < num_options && !are_equal; j++) {
+				if (xfcekbd_keyboard_config_split_items
+				    (kbd_config2->options[j], &group2,
+				     &option2)) {
+					are_equal =
+					    strcmp (option1, option2) == 0;
+				}
+			}
+
+			g_free (option1);
+
+			if (!are_equal)
+				return False;
+		}
+	}
+
+	return True;
+}
+
+gboolean
+xfcekbd_keyboard_config_equals (XfcekbdKeyboardConfig * kbd_config1,
+			     XfcekbdKeyboardConfig * kbd_config2)
+{
+	if (kbd_config1 == kbd_config2)
+		return True;
+	if ((kbd_config1->model != kbd_config2->model) &&
+	    (kbd_config1->model != NULL) &&
+	    (kbd_config2->model != NULL) &&
+	    g_ascii_strcasecmp (kbd_config1->model, kbd_config2->model))
+		return False;
+	if (!g_strv_equal (kbd_config1->layouts_variants,
+			   kbd_config2->layouts_variants))
+		return False;
+
+	if (!xfcekbd_keyboard_config_options_equals
+	    (kbd_config1, kbd_config2))
+		return False;
+
+	return True;
+}
+
+void
+xfcekbd_keyboard_config_save_to_gsettings (XfcekbdKeyboardConfig * kbd_config)
+{
+	g_settings_delay (kbd_config->settings);
+
+	xfcekbd_keyboard_config_save_params (kbd_config,
+					     XFCEKBD_KEYBOARD_CONFIG_ACTIVE);
+
+	g_settings_apply (kbd_config->settings);
+}
+
+void
+xfcekbd_keyboard_config_model_set (XfcekbdKeyboardConfig * kbd_config,
+				const gchar * model_name)
+{
+	if (kbd_config->model != NULL)
+		g_free (kbd_config->model);
+	kbd_config->model =
+	    (model_name == NULL
+	     || model_name[0] == '\0') ? NULL : g_strdup (model_name);
+}
+
+void
+xfcekbd_keyboard_config_options_set (XfcekbdKeyboardConfig * kbd_config,
+				  gint idx,
+				  const gchar * group_name,
+				  const gchar * option_name)
+{
+	const gchar *merged;
+	if (group_name == NULL || option_name == NULL)
+		return;
+	merged =
+	    xfcekbd_keyboard_config_merge_items (group_name, option_name);
+	if (merged == NULL)
+		return;
+	kbd_config->options[idx] = g_strdup (merged);
+}
+
+gboolean
+xfcekbd_keyboard_config_options_is_set (XfcekbdKeyboardConfig * kbd_config,
+				     const gchar * group_name,
+				     const gchar * option_name)
+{
+	gchar **p = kbd_config->options;
+	const gchar *merged =
+	    xfcekbd_keyboard_config_merge_items (group_name, option_name);
+	if (merged == NULL)
+		return FALSE;
+
+	while (p && *p) {
+		if (!g_ascii_strcasecmp (merged, *p++))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+gboolean
+xfcekbd_keyboard_config_activate (XfcekbdKeyboardConfig * kbd_config)
+{
+	gboolean rv;
+	XklConfigRec *data = xkl_config_rec_new ();
+
+	xfcekbd_keyboard_config_copy_to_xkl_config (kbd_config, data);
+	rv = xkl_config_rec_activate (data, kbd_config->engine);
+	g_object_unref (G_OBJECT (data));
+
+	return rv;
+}
+
+/**
+ * xfcekbd_keyboard_config_start_listen:
+ * @func: (scope notified): a function to call when settings are changed
+ */
+void
+xfcekbd_keyboard_config_start_listen (XfcekbdKeyboardConfig * kbd_config,
+				   GCallback func,
+				   gpointer user_data)
+{
+	kbd_config->config_listener_id =
+	    g_signal_connect (kbd_config->settings, "changed", func,
+			      user_data);
+}
+
+void
+xfcekbd_keyboard_config_stop_listen (XfcekbdKeyboardConfig * kbd_config)
+{
+	g_signal_handler_disconnect (kbd_config->settings,
+				     kbd_config->config_listener_id);
+	kbd_config->config_listener_id = 0;
+}
+
+gboolean
+xfcekbd_keyboard_config_get_descriptions (XklConfigRegistry * config_registry,
+				       const gchar * name,
+				       gchar ** layout_short_descr,
+				       gchar ** layout_descr,
+				       gchar ** variant_short_descr,
+				       gchar ** variant_descr)
+{
+	char *layout_name = NULL, *variant_name = NULL;
+	if (!xfcekbd_keyboard_config_split_items
+	    (name, &layout_name, &variant_name))
+		return FALSE;
+	return xfcekbd_keyboard_config_get_lv_descriptions (config_registry,
+							 layout_name,
+							 variant_name,
+							 layout_short_descr,
+							 layout_descr,
+							 variant_short_descr,
+							 variant_descr);
+}
+
+const gchar *
+xfcekbd_keyboard_config_format_full_layout (const gchar * layout_descr,
+					 const gchar * variant_descr)
+{
+	static gchar full_descr[XKL_MAX_CI_DESC_LENGTH * 2];
+	if (variant_descr == NULL || variant_descr[0] == 0)
+		g_snprintf (full_descr, sizeof (full_descr), "%s",
+			    layout_descr);
+	else
+		g_snprintf (full_descr, sizeof (full_descr), "%s %s",
+			    layout_descr, variant_descr);
+	return full_descr;
+}
+
+gchar *
+xfcekbd_keyboard_config_to_string (const XfcekbdKeyboardConfig * config)
+{
+	gchar *layouts = NULL, *options = NULL;
+	GString *buffer = g_string_new (NULL);
+
+	gchar **iter;
+	gint count;
+	gchar *result;
+
+	if (config->layouts_variants) {
+		/* g_slist_length is "expensive", so we determinate the length on the fly */
+		for (iter = config->layouts_variants, count = 0; *iter;
+		     iter++, ++count) {
+			if (buffer->len)
+				g_string_append (buffer, " ");
+
+			g_string_append (buffer, *iter);
+		}
+
+		/* Translators: The count is related to the number of options. The %s
+		 * format specifier should not be modified, left "as is". */
+		layouts =
+		    g_strdup_printf (ngettext
+				     ("layout \"%s\"", "layouts \"%s\"",
+				      count), buffer->str);
+		g_string_truncate (buffer, 0);
+	}
+	if (config->options) {
+		/* g_slist_length is "expensive", so we determinate the length on the fly */
+		for (iter = config->options, count = 0; *iter;
+		     iter++, ++count) {
+			if (buffer->len)
+				g_string_append (buffer, " ");
+
+			g_string_append (buffer, *iter);
+		}
+
+		/* Translators: The count is related to the number of options. The %s
+		 * format specifier should not be modified, left "as is". */
+		options =
+		    g_strdup_printf (ngettext
+				     ("option \"%s\"", "options \"%s\"",
+				      count), buffer->str);
+		g_string_truncate (buffer, 0);
+	}
+
+	g_string_free (buffer, TRUE);
+
+	result =
+	    g_strdup_printf (_("model \"%s\", %s and %s"), config->model,
+			     layouts ? layouts : _("no layout"),
+			     options ? options : _("no options"));
+
+	g_free (options);
+	g_free (layouts);
+
+	return result;
+}
+
+/**
+ * xfcekbd_keyboard_config_add_default_switch_option_if_necessary:
+ *
+ * Returns: (transfer full) (array zero-terminated=1): List of options
+ */
+gchar **
+xfcekbd_keyboard_config_add_default_switch_option_if_necessary (gchar **
+							        layouts_list,
+							        gchar **
+							        options_list,
+							        gboolean *was_appended)
+{
+	*was_appended = FALSE;
+	if (g_strv_length (layouts_list) >= 2) {
+		gboolean any_switcher = False;
+		if (*options_list != NULL) {
+			gchar **option = options_list;
+			while (*option != NULL) {
+				char *g, *o;
+				if (xfcekbd_keyboard_config_split_items
+				    (*option, &g, &o)) {
+					if (!g_ascii_strcasecmp
+					    (g, GROUP_SWITCHERS_GROUP)) {
+						any_switcher = True;
+						break;
+					}
+				}
+				option++;
+			}
+		}
+		if (!any_switcher) {
+			const gchar *id =
+			    xfcekbd_keyboard_config_merge_items
+			    (GROUP_SWITCHERS_GROUP,
+			     DEFAULT_GROUP_SWITCH);
+			options_list =
+			    xfcekbd_strv_append (options_list, g_strdup (id));
+			*was_appended = TRUE;
+		}
+	}
+	return options_list;
+}
diff --git a/src/xfcekbd-keyboard-config.h b/src/xfcekbd-keyboard-config.h
new file mode 100644
index 0000000..3ce46ed
--- /dev/null
+++ b/src/xfcekbd-keyboard-config.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2006 Sergey V. Udaltsov <svu at gnome.org>
+ *
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __XFCEKBD_KEYBOARD_CONFIG_H__
+#define __XFCEKBD_KEYBOARD_CONFIG_H__
+
+#include <X11/Xlib.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <libxklavier/xklavier.h>
+
+extern const gchar XFCEKBD_KEYBOARD_CONFIG_KEY_MODEL[];
+extern const gchar XFCEKBD_KEYBOARD_CONFIG_KEY_LAYOUTS[];
+extern const gchar XFCEKBD_KEYBOARD_CONFIG_KEY_OPTIONS[];
+
+/*
+ * Keyboard Configuration
+ */
+typedef struct _XfcekbdKeyboardConfig XfcekbdKeyboardConfig;
+struct _XfcekbdKeyboardConfig {
+	gchar *model;
+	gchar **layouts_variants;
+	gchar **options;
+
+	/* private, transient */
+	GSettings *settings;
+	int config_listener_id;
+	XklEngine *engine;
+};
+
+/*
+ * XfcekbdKeyboardConfig functions
+ */
+extern void xfcekbd_keyboard_config_init (XfcekbdKeyboardConfig * kbd_config,
+				       XklEngine * engine);
+extern void xfcekbd_keyboard_config_term (XfcekbdKeyboardConfig * kbd_config);
+
+extern void xfcekbd_keyboard_config_load_from_gsettings (XfcekbdKeyboardConfig *
+						  kbd_config,
+						  XfcekbdKeyboardConfig *
+						  kbd_config_default);
+
+extern void xfcekbd_keyboard_config_save_to_gsettings (XfcekbdKeyboardConfig *
+						kbd_config);
+
+extern void xfcekbd_keyboard_config_load_from_x_initial (XfcekbdKeyboardConfig *
+						      kbd_config,
+						      XklConfigRec * buf);
+
+extern void xfcekbd_keyboard_config_load_from_x_current (XfcekbdKeyboardConfig *
+						      kbd_config,
+						      XklConfigRec * buf);
+
+extern void xfcekbd_keyboard_config_start_listen (XfcekbdKeyboardConfig *
+					       kbd_config,
+					       GCallback func,
+					       gpointer user_data);
+
+extern void xfcekbd_keyboard_config_stop_listen (XfcekbdKeyboardConfig *
+					      kbd_config);
+
+extern gboolean xfcekbd_keyboard_config_equals (XfcekbdKeyboardConfig *
+					     kbd_config1,
+					     XfcekbdKeyboardConfig *
+					     kbd_config2);
+
+extern gboolean xfcekbd_keyboard_config_activate (XfcekbdKeyboardConfig *
+					       kbd_config);
+
+extern const gchar *xfcekbd_keyboard_config_merge_items (const gchar * parent,
+						      const gchar * child);
+
+extern gboolean xfcekbd_keyboard_config_split_items (const gchar * merged,
+						  gchar ** parent,
+						  gchar ** child);
+
+extern gboolean xfcekbd_keyboard_config_get_descriptions (XklConfigRegistry *
+						       config_registry,
+						       const gchar * name,
+						       gchar **
+						       layout_short_descr,
+						       gchar **
+						       layout_descr,
+						       gchar **
+						       variant_short_descr,
+						       gchar **
+						       variant_descr);
+
+extern const gchar *xfcekbd_keyboard_config_format_full_layout (const gchar
+							     *
+							     layout_descr,
+							     const gchar *
+							     variant_descr);
+
+extern gchar *xfcekbd_keyboard_config_to_string (const XfcekbdKeyboardConfig *
+					      config);
+
+extern gchar
+    **xfcekbd_keyboard_config_add_default_switch_option_if_necessary (gchar **
+								  layouts_list,
+								  gchar **
+								  options_list,
+								  gboolean
+								  *
+								  was_appended);
+
+#endif
diff --git a/src/xfcekbd-util.c b/src/xfcekbd-util.c
new file mode 100644
index 0000000..4f20cbe
--- /dev/null
+++ b/src/xfcekbd-util.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2006 Sergey V. Udaltsov <svu at gnome.org>
+ *
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <config.h>
+
+#include <xfcekbd-util.h>
+
+#include <time.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <libxklavier/xklavier.h>
+
+#include <gio/gio.h>
+
+#include <gdk/gdkx.h>
+
+#include <xfcekbd-config-private.h>
+
+static void
+xfcekbd_log_appender (const char file[], const char function[],
+		   int level, const char format[], va_list args)
+{
+	time_t now = time (NULL);
+	g_log (NULL, G_LOG_LEVEL_DEBUG, "[%08ld,%03d,%s:%s/] \t",
+	       (long) now, level, file, function);
+	g_logv (NULL, G_LOG_LEVEL_DEBUG, format, args);
+}
+
+void
+xfcekbd_install_glib_log_appender (void)
+{
+	xkl_set_log_appender (xfcekbd_log_appender);
+}
+
+#define MATEKBD_PREVIEW_CONFIG_SCHEMA  XFCEKBD_CONFIG_SCHEMA ".preview"
+
+const gchar MATEKBD_PREVIEW_CONFIG_KEY_X[] = "x";
+const gchar MATEKBD_PREVIEW_CONFIG_KEY_Y[] = "y";
+const gchar MATEKBD_PREVIEW_CONFIG_KEY_WIDTH[] = "width";
+const gchar MATEKBD_PREVIEW_CONFIG_KEY_HEIGHT[] = "height";
+
+/**
+ * xfcekbd_preview_load_position:
+ *
+ * Returns: (transfer full): A rectangle to use
+ */
+GdkRectangle *
+xfcekbd_preview_load_position (void)
+{
+	GdkRectangle *rv = NULL;
+	gint x, y, w, h;
+	GSettings* settings = g_settings_new (MATEKBD_PREVIEW_CONFIG_SCHEMA);
+
+	x = g_settings_get_int (settings, MATEKBD_PREVIEW_CONFIG_KEY_X);
+	y = g_settings_get_int (settings, MATEKBD_PREVIEW_CONFIG_KEY_Y);
+	w = g_settings_get_int (settings, MATEKBD_PREVIEW_CONFIG_KEY_WIDTH);
+	h = g_settings_get_int (settings, MATEKBD_PREVIEW_CONFIG_KEY_HEIGHT);
+
+	g_object_unref (settings);
+
+	rv = g_new (GdkRectangle, 1);
+	if (x == -1 || y == -1 || w == -1 || h == -1) {
+		/* default values should be treated as
+		 * "0.75 of the screen size" */
+		GdkScreen *scr = gdk_screen_get_default ();
+		gint w = WidthOfScreen (gdk_x11_screen_get_xscreen (scr));
+		gint h = HeightOfScreen (gdk_x11_screen_get_xscreen (scr));
+		rv->x = w >> 3;
+		rv->y = h >> 3;
+		rv->width = w - (w >> 2);
+		rv->height = h - (h >> 2);
+	} else {
+		rv->x = x;
+		rv->y = y;
+		rv->width = w;
+		rv->height = h;
+	}
+	return rv;
+}
+
+void
+xfcekbd_preview_save_position (GdkRectangle * rect)
+{
+	GSettings* settings = g_settings_new (MATEKBD_PREVIEW_CONFIG_SCHEMA);
+
+	g_settings_delay (settings);
+
+	g_settings_set_int (settings, MATEKBD_PREVIEW_CONFIG_KEY_X, rect->x);
+	g_settings_set_int (settings, MATEKBD_PREVIEW_CONFIG_KEY_Y, rect->y);
+	g_settings_set_int (settings, MATEKBD_PREVIEW_CONFIG_KEY_WIDTH, rect->width);
+	g_settings_set_int (settings, MATEKBD_PREVIEW_CONFIG_KEY_HEIGHT, rect->height);
+
+	g_settings_apply (settings);
+
+	g_object_unref (settings);
+}
+
+/**
+ * xfcekbd_strv_append:
+ *
+ * Returns: (transfer full) (array zero-terminated=1): Append string to strv array
+ */
+gchar **
+xfcekbd_strv_append (gchar ** arr, gchar * element)
+{
+	gint old_length = (arr == NULL) ? 0 : g_strv_length (arr);
+	gchar **new_arr = g_new0 (gchar *, old_length + 2);
+	if (arr != NULL) {
+		memcpy (new_arr, arr, old_length * sizeof (gchar *));
+		g_free (arr);
+	}
+	new_arr[old_length] = element;
+	return new_arr;
+}
+
diff --git a/src/xfcekbd-util.h b/src/xfcekbd-util.h
new file mode 100644
index 0000000..aff928e
--- /dev/null
+++ b/src/xfcekbd-util.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2006 Sergey V. Udaltsov <svu at gnome.org>
+ *
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __XFCEKBD_UTIL_H__
+#define __XFCEKBD_UTIL_H__
+
+#include <glib.h>
+#include <gdk/gdk.h>
+
+extern void xfcekbd_install_glib_log_appender (void);
+
+extern GdkRectangle *xfcekbd_preview_load_position (void);
+
+extern void xfcekbd_preview_save_position (GdkRectangle * rect);
+
+/* Missing in glib */
+extern gchar **xfcekbd_strv_append (gchar ** arr, gchar * element);
+
+
+#endif
diff --git a/src/xfcemenu-tree.c b/src/xfcemenu-tree.c
new file mode 100644
index 0000000..8f11427
--- /dev/null
+++ b/src/xfcemenu-tree.c
@@ -0,0 +1,4556 @@
+/*
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <config.h>
+
+#include "xfcemenu-tree.h"
+
+#include <gio/gio.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
+} XfceMenuTreeType;
+
+struct XfceMenuTree
+{
+  XfceMenuTreeType type;
+  guint         refcount;
+
+  char *basename;
+  char *absolute_path;
+  char *canonical_path;
+
+  XfceMenuTreeFlags flags;
+  XfceMenuTreeSortKey sort_key;
+
+  GSList *menu_file_monitors;
+
+  MenuLayoutNode *layout;
+  XfceMenuTreeDirectory *root;
+
+  GSList *monitors;
+
+  gpointer       user_data;
+  GDestroyNotify dnotify;
+
+  guint canonical : 1;
+};
+
+typedef struct
+{
+  XfceMenuTreeChangedFunc callback;
+  gpointer             user_data;
+} XfceMenuTreeMonitor;
+
+struct XfceMenuTreeItem
+{
+  XfceMenuTreeItemType type;
+
+  XfceMenuTreeDirectory *parent;
+
+  gpointer       user_data;
+  GDestroyNotify dnotify;
+
+  guint refcount;
+};
+
+struct XfceMenuTreeDirectory
+{
+	XfceMenuTreeItem 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
+{
+  XfceMenuTreeDirectory directory;
+
+  XfceMenuTree *tree;
+} XfceMenuTreeDirectoryRoot;
+
+struct XfceMenuTreeEntry
+{
+  XfceMenuTreeItem item;
+
+  DesktopEntry *desktop_entry;
+  char         *desktop_file_id;
+
+  guint is_excluded : 1;
+  guint is_nodisplay : 1;
+};
+
+struct XfceMenuTreeSeparator
+{
+  XfceMenuTreeItem item;
+};
+
+struct XfceMenuTreeHeader
+{
+  XfceMenuTreeItem item;
+
+  XfceMenuTreeDirectory *directory;
+};
+
+struct XfceMenuTreeAlias
+{
+  XfceMenuTreeItem item;
+
+  XfceMenuTreeDirectory *directory;
+  XfceMenuTreeItem      *aliased_item;
+};
+
+static XfceMenuTree *xfcemenu_tree_new                 (XfceMenuTreeType    type,
+						  const char      *menu_file,
+						  gboolean         canonical,
+						  XfceMenuTreeFlags   flags);
+static void      xfcemenu_tree_load_layout          (XfceMenuTree       *tree);
+static void      xfcemenu_tree_force_reload         (XfceMenuTree       *tree);
+static void      xfcemenu_tree_build_from_layout    (XfceMenuTree       *tree);
+static void      xfcemenu_tree_force_rebuild        (XfceMenuTree       *tree);
+static void      xfcemenu_tree_resolve_files        (XfceMenuTree       *tree,
+						  GHashTable      *loaded_menu_files,
+						  MenuLayoutNode  *layout);
+static void      xfcemenu_tree_force_recanonicalize (XfceMenuTree       *tree);
+static void      xfcemenu_tree_invoke_monitors      (XfceMenuTree       *tree);
+
+static void xfcemenu_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 *xfcemenu_tree_cache = NULL;
+
+static inline char *
+get_cache_key (XfceMenuTree      *tree,
+	       XfceMenuTreeFlags  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
+xfcemenu_tree_add_to_cache (XfceMenuTree      *tree,
+			 XfceMenuTreeFlags  flags)
+{
+  char *cache_key;
+
+  if (xfcemenu_tree_cache == NULL)
+    {
+      xfcemenu_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 (xfcemenu_tree_cache, cache_key, tree);
+}
+
+static void
+xfcemenu_tree_remove_from_cache (XfceMenuTree      *tree,
+			      XfceMenuTreeFlags  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 (xfcemenu_tree_cache, cache_key);
+
+  g_free (cache_key);
+
+  if (g_hash_table_size (xfcemenu_tree_cache) == 0)
+    {
+      g_hash_table_destroy (xfcemenu_tree_cache);
+      xfcemenu_tree_cache = NULL;
+
+      _entry_directory_list_empty_desktop_cache ();
+    }
+}
+
+static XfceMenuTree *
+xfcemenu_tree_lookup_from_cache (const char    *tree_name,
+			      XfceMenuTreeFlags  flags)
+{
+  XfceMenuTree *retval;
+  char     *cache_key;
+
+  if (xfcemenu_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 (xfcemenu_tree_cache, cache_key);
+
+  g_free (cache_key);
+
+  return retval ? xfcemenu_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,
+				      XfceMenuTree        *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");
+
+      xfcemenu_tree_force_recanonicalize (tree);
+      xfcemenu_tree_invoke_monitors (tree);
+    }
+}
+
+static void
+handle_menu_file_changed (MenuMonitor      *monitor,
+			  MenuMonitorEvent  event,
+			  const char       *path,
+                          XfceMenuTree        *tree)
+{
+  menu_verbose ("\"%s\" %s, marking tree for recanicalization\n",
+		path,
+		event == MENU_MONITOR_EVENT_CREATED ? "created" :
+		event == MENU_MONITOR_EVENT_CHANGED ? "changed" : "deleted");
+
+  xfcemenu_tree_force_recanonicalize (tree);
+  xfcemenu_tree_invoke_monitors (tree);
+}
+
+static void
+handle_menu_file_directory_changed (MenuMonitor      *monitor,
+				    MenuMonitorEvent  event,
+				    const char       *path,
+				    XfceMenuTree        *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");
+
+  xfcemenu_tree_force_recanonicalize (tree);
+  xfcemenu_tree_invoke_monitors (tree);
+}
+
+static void
+xfcemenu_tree_add_menu_file_monitor (XfceMenuTree           *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,
+			  XfceMenuTree       *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
+xfcemenu_tree_remove_menu_file_monitors (XfceMenuTree *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 XfceMenuTree *
+xfcemenu_tree_lookup_absolute (const char    *absolute,
+			    XfceMenuTreeFlags  flags)
+{
+  XfceMenuTree  *tree;
+  gboolean    canonical;
+  const char *canonical_path;
+  char       *freeme;
+
+  menu_verbose ("Looking up absolute path in tree cache: \"%s\"\n", absolute);
+
+  if ((tree = xfcemenu_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 = xfcemenu_tree_lookup_from_cache (canonical_path, flags)) != NULL)
+    return tree;
+
+  tree = xfcemenu_tree_new (MATEMENU_TREE_ABSOLUTE, canonical_path, canonical, flags);
+
+  g_free (freeme);
+
+  return tree;
+}
+
+static XfceMenuTree *
+xfcemenu_tree_lookup_basename (const char    *basename,
+			    XfceMenuTreeFlags  flags)
+{
+  XfceMenuTree *tree;
+
+  menu_verbose ("Looking up menu file in tree cache: \"%s\"\n", basename);
+
+  if ((tree = xfcemenu_tree_lookup_from_cache (basename, flags)) != NULL)
+    return tree;
+
+  return xfcemenu_tree_new (MATEMENU_TREE_BASENAME, basename, FALSE, flags);
+}
+
+static gboolean
+canonicalize_basename_with_config_dir (XfceMenuTree   *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;
+      xfcemenu_tree_add_menu_file_monitor (tree,
+					tree->canonical_path,
+					MENU_FILE_MONITOR_FILE);
+    }
+  else
+    {
+      xfcemenu_tree_add_menu_file_monitor (tree,
+					path,
+					MENU_FILE_MONITOR_NONEXISTENT_FILE);
+    }
+
+  g_free (path);
+
+  return tree->canonical;
+}
+
+static void
+canonicalize_basename (XfceMenuTree  *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 xfcemenu_tree_canonicalize_path(XfceMenuTree* tree)
+{
+	if (tree->canonical)
+		return TRUE;
+
+	g_assert(tree->canonical_path == NULL);
+
+	if (tree->type == MATEMENU_TREE_BASENAME)
+	{
+		xfcemenu_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
+			*/
+			xfcemenu_tree_remove_from_cache (tree, tree->flags);
+
+			xfcemenu_tree_remove_menu_file_monitors(tree);
+			xfcemenu_tree_add_menu_file_monitor(tree, tree->canonical_path, MENU_FILE_MONITOR_FILE);
+
+			tree->canonical = TRUE;
+
+			xfcemenu_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
+xfcemenu_tree_force_recanonicalize (XfceMenuTree *tree)
+{
+  xfcemenu_tree_remove_menu_file_monitors (tree);
+
+  if (tree->canonical)
+    {
+      xfcemenu_tree_force_reload (tree);
+
+      g_free (tree->canonical_path);
+      tree->canonical_path = NULL;
+
+      tree->canonical = FALSE;
+    }
+}
+
+XfceMenuTree* xfcemenu_tree_lookup(const char* menu_file, XfceMenuTreeFlags flags)
+{
+  XfceMenuTree *retval;
+
+  g_return_val_if_fail (menu_file != NULL, NULL);
+
+  flags &= MATEMENU_TREE_FLAGS_MASK;
+
+  if (g_path_is_absolute (menu_file))
+    retval = xfcemenu_tree_lookup_absolute (menu_file, flags);
+  else
+    retval = xfcemenu_tree_lookup_basename (menu_file, flags);
+
+  g_assert (retval != NULL);
+
+  return retval;
+}
+
+static XfceMenuTree *
+xfcemenu_tree_new (XfceMenuTreeType   type,
+		const char     *menu_file,
+		gboolean        canonical,
+		XfceMenuTreeFlags  flags)
+{
+  XfceMenuTree *tree;
+
+  tree = g_new0 (XfceMenuTree, 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);
+	  xfcemenu_tree_add_menu_file_monitor (tree,
+					    tree->canonical_path,
+					    MENU_FILE_MONITOR_FILE);
+	}
+      else
+	{
+	  xfcemenu_tree_add_menu_file_monitor (tree,
+					    tree->absolute_path,
+					    MENU_FILE_MONITOR_NONEXISTENT_FILE);
+	}
+    }
+
+  xfcemenu_tree_add_to_cache (tree, tree->flags);
+
+  return tree;
+}
+
+XfceMenuTree *
+xfcemenu_tree_ref (XfceMenuTree *tree)
+{
+  g_return_val_if_fail (tree != NULL, NULL);
+  g_return_val_if_fail (tree->refcount > 0, NULL);
+
+  tree->refcount++;
+
+  return tree;
+}
+
+void
+xfcemenu_tree_unref (XfceMenuTree *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;
+
+  xfcemenu_tree_remove_from_cache (tree, tree->flags);
+
+  xfcemenu_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
+xfcemenu_tree_set_user_data (XfceMenuTree       *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
+xfcemenu_tree_get_user_data (XfceMenuTree *tree)
+{
+  g_return_val_if_fail (tree != NULL, NULL);
+
+  return tree->user_data;
+}
+
+const char *
+xfcemenu_tree_get_menu_file (XfceMenuTree *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 (!xfcemenu_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;
+}
+
+XfceMenuTreeDirectory *
+xfcemenu_tree_get_root_directory (XfceMenuTree *tree)
+{
+  g_return_val_if_fail (tree != NULL, NULL);
+
+  if (!tree->root)
+    {
+      xfcemenu_tree_build_from_layout (tree);
+
+      if (!tree->root)
+        return NULL;
+    }
+
+  return xfcemenu_tree_item_ref (tree->root);
+}
+
+static XfceMenuTreeDirectory *
+find_path (XfceMenuTreeDirectory *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)
+    {
+      XfceMenuTreeItem *item = tmp->data;
+
+      if (xfcemenu_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;
+}
+
+XfceMenuTreeDirectory *
+xfcemenu_tree_get_directory_from_path (XfceMenuTree  *tree,
+				    const char *path)
+{
+  XfceMenuTreeDirectory *root;
+  XfceMenuTreeDirectory *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 = xfcemenu_tree_get_root_directory (tree)))
+    return NULL;
+
+  directory = find_path (root, path);
+
+  xfcemenu_tree_item_unref (root);
+
+  return directory ? xfcemenu_tree_item_ref (directory) : NULL;
+}
+
+XfceMenuTreeSortKey
+xfcemenu_tree_get_sort_key (XfceMenuTree *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
+xfcemenu_tree_set_sort_key (XfceMenuTree        *tree,
+			 XfceMenuTreeSortKey  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;
+  xfcemenu_tree_force_rebuild (tree);
+}
+
+void
+xfcemenu_tree_add_monitor (XfceMenuTree            *tree,
+                       XfceMenuTreeChangedFunc   callback,
+                       gpointer               user_data)
+{
+  XfceMenuTreeMonitor *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 (XfceMenuTreeMonitor, 1);
+
+      monitor->callback  = callback;
+      monitor->user_data = user_data;
+
+      tree->monitors = g_slist_append (tree->monitors, monitor);
+    }
+}
+
+void
+xfcemenu_tree_remove_monitor (XfceMenuTree            *tree,
+			   XfceMenuTreeChangedFunc  callback,
+			   gpointer              user_data)
+{
+  GSList *tmp;
+
+  g_return_if_fail (tree != NULL);
+  g_return_if_fail (callback != NULL);
+
+  tmp = tree->monitors;
+  while (tmp != NULL)
+    {
+      XfceMenuTreeMonitor *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
+xfcemenu_tree_invoke_monitors (XfceMenuTree *tree)
+{
+  GSList *tmp;
+
+  tmp = tree->monitors;
+  while (tmp != NULL)
+    {
+      XfceMenuTreeMonitor *monitor = tmp->data;
+      GSList           *next    = tmp->next;
+
+      monitor->callback (tree, monitor->user_data);
+
+      tmp = next;
+    }
+}
+
+XfceMenuTreeItemType
+xfcemenu_tree_item_get_type (XfceMenuTreeItem *item)
+{
+  g_return_val_if_fail (item != NULL, 0);
+
+  return item->type;
+}
+
+XfceMenuTreeDirectory *
+xfcemenu_tree_item_get_parent (XfceMenuTreeItem *item)
+{
+  g_return_val_if_fail (item != NULL, NULL);
+
+  return item->parent ? xfcemenu_tree_item_ref (item->parent) : NULL;
+}
+
+static void
+xfcemenu_tree_item_set_parent (XfceMenuTreeItem      *item,
+			    XfceMenuTreeDirectory *parent)
+{
+  g_return_if_fail (item != NULL);
+
+  item->parent = parent;
+}
+
+GSList *
+xfcemenu_tree_directory_get_contents (XfceMenuTreeDirectory *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,
+                                xfcemenu_tree_item_ref (tmp->data));
+
+      tmp = tmp->next;
+    }
+
+  return g_slist_reverse (retval);
+}
+
+const char *
+xfcemenu_tree_directory_get_name (XfceMenuTreeDirectory *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 *
+xfcemenu_tree_directory_get_comment (XfceMenuTreeDirectory *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* xfcemenu_tree_directory_get_icon(XfceMenuTreeDirectory* 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 *
+xfcemenu_tree_directory_get_desktop_file_path (XfceMenuTreeDirectory *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 *
+xfcemenu_tree_directory_get_menu_id (XfceMenuTreeDirectory *directory)
+{
+  g_return_val_if_fail (directory != NULL, NULL);
+
+  return directory->name;
+}
+
+static void
+xfcemenu_tree_directory_set_tree (XfceMenuTreeDirectory *directory,
+			       XfceMenuTree          *tree)
+{
+  XfceMenuTreeDirectoryRoot *root;
+
+  g_assert (directory != NULL);
+  g_assert (directory->is_root);
+
+  root = (XfceMenuTreeDirectoryRoot *) directory;
+
+  root->tree = tree;
+}
+
+XfceMenuTree *
+xfcemenu_tree_directory_get_tree (XfceMenuTreeDirectory *directory)
+{
+  XfceMenuTreeDirectoryRoot *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 = (XfceMenuTreeDirectoryRoot *) directory;
+
+  if (root->tree)
+    xfcemenu_tree_ref (root->tree);
+
+  return root->tree;
+}
+
+gboolean
+xfcemenu_tree_directory_get_is_nodisplay (XfceMenuTreeDirectory *directory)
+{
+  g_return_val_if_fail (directory != NULL, FALSE);
+
+  return directory->is_nodisplay;
+}
+
+static void
+append_directory_path (XfceMenuTreeDirectory *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 *
+xfcemenu_tree_directory_make_path (XfceMenuTreeDirectory *directory,
+				XfceMenuTreeEntry     *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 *
+xfcemenu_tree_entry_get_name (XfceMenuTreeEntry *entry)
+{
+  g_return_val_if_fail (entry != NULL, NULL);
+
+  return desktop_entry_get_name (entry->desktop_entry);
+}
+
+const char *
+xfcemenu_tree_entry_get_generic_name (XfceMenuTreeEntry *entry)
+{
+  g_return_val_if_fail (entry != NULL, NULL);
+
+  return desktop_entry_get_generic_name (entry->desktop_entry);
+}
+
+const char *
+xfcemenu_tree_entry_get_display_name (XfceMenuTreeEntry *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 *
+xfcemenu_tree_entry_get_comment (XfceMenuTreeEntry *entry)
+{
+  g_return_val_if_fail (entry != NULL, NULL);
+
+  return desktop_entry_get_comment (entry->desktop_entry);
+}
+
+const char* xfcemenu_tree_entry_get_icon(XfceMenuTreeEntry *entry)
+{
+	g_return_val_if_fail (entry != NULL, NULL);
+
+	return desktop_entry_get_icon(entry->desktop_entry);
+}
+
+const char* xfcemenu_tree_entry_get_exec(XfceMenuTreeEntry* entry)
+{
+	g_return_val_if_fail(entry != NULL, NULL);
+
+	return desktop_entry_get_exec(entry->desktop_entry);
+}
+
+gboolean xfcemenu_tree_entry_get_launch_in_terminal(XfceMenuTreeEntry* entry)
+{
+  g_return_val_if_fail(entry != NULL, FALSE);
+
+  return desktop_entry_get_launch_in_terminal(entry->desktop_entry);
+}
+
+const char* xfcemenu_tree_entry_get_desktop_file_path(XfceMenuTreeEntry* entry)
+{
+	g_return_val_if_fail(entry != NULL, NULL);
+
+	return desktop_entry_get_path(entry->desktop_entry);
+}
+
+const char* xfcemenu_tree_entry_get_desktop_file_id(XfceMenuTreeEntry* entry)
+{
+	g_return_val_if_fail(entry != NULL, NULL);
+
+	return entry->desktop_file_id;
+}
+
+gboolean xfcemenu_tree_entry_get_is_excluded(XfceMenuTreeEntry* entry)
+{
+	g_return_val_if_fail(entry != NULL, FALSE);
+
+	return entry->is_excluded;
+}
+
+gboolean xfcemenu_tree_entry_get_is_nodisplay(XfceMenuTreeEntry* entry)
+{
+	g_return_val_if_fail(entry != NULL, FALSE);
+
+	return entry->is_nodisplay;
+}
+
+XfceMenuTreeDirectory* xfcemenu_tree_header_get_directory(XfceMenuTreeHeader* header)
+{
+	g_return_val_if_fail (header != NULL, NULL);
+
+	return xfcemenu_tree_item_ref(header->directory);
+}
+
+XfceMenuTreeDirectory* xfcemenu_tree_alias_get_directory(XfceMenuTreeAlias* alias)
+{
+	g_return_val_if_fail (alias != NULL, NULL);
+
+	return xfcemenu_tree_item_ref(alias->directory);
+}
+
+XfceMenuTreeItem *
+xfcemenu_tree_alias_get_item (XfceMenuTreeAlias *alias)
+{
+  g_return_val_if_fail (alias != NULL, NULL);
+
+  return xfcemenu_tree_item_ref (alias->aliased_item);
+}
+
+static XfceMenuTreeDirectory *
+xfcemenu_tree_directory_new (XfceMenuTreeDirectory *parent,
+			  const char         *name,
+			  gboolean            is_root)
+{
+  XfceMenuTreeDirectory *retval;
+
+  if (!is_root)
+    {
+      retval = g_new0 (XfceMenuTreeDirectory, 1);
+    }
+  else
+    {
+      XfceMenuTreeDirectoryRoot *root;
+
+      root = g_new0 (XfceMenuTreeDirectoryRoot, 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
+xfcemenu_tree_directory_finalize (XfceMenuTreeDirectory *directory)
+{
+  g_assert (directory->item.refcount == 0);
+
+  g_slist_foreach (directory->contents,
+		   (GFunc) xfcemenu_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) xfcemenu_tree_item_unref_and_unset_parent,
+		   NULL);
+  g_slist_free (directory->subdirs);
+  directory->subdirs = NULL;
+
+  g_slist_foreach (directory->entries,
+		   (GFunc) xfcemenu_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 XfceMenuTreeSeparator *
+xfcemenu_tree_separator_new (XfceMenuTreeDirectory *parent)
+{
+  XfceMenuTreeSeparator *retval;
+
+  retval = g_new0 (XfceMenuTreeSeparator, 1);
+
+  retval->item.type     = MATEMENU_TREE_ITEM_SEPARATOR;
+  retval->item.parent   = parent;
+  retval->item.refcount = 1;
+
+  return retval;
+}
+
+static XfceMenuTreeHeader *
+xfcemenu_tree_header_new (XfceMenuTreeDirectory *parent,
+		       XfceMenuTreeDirectory *directory)
+{
+  XfceMenuTreeHeader *retval;
+
+  retval = g_new0 (XfceMenuTreeHeader, 1);
+
+  retval->item.type     = MATEMENU_TREE_ITEM_HEADER;
+  retval->item.parent   = parent;
+  retval->item.refcount = 1;
+
+  retval->directory = xfcemenu_tree_item_ref (directory);
+
+  xfcemenu_tree_item_set_parent (MATEMENU_TREE_ITEM (retval->directory), NULL);
+
+  return retval;
+}
+
+static void
+xfcemenu_tree_header_finalize (XfceMenuTreeHeader *header)
+{
+  g_assert (header->item.refcount == 0);
+
+  if (header->directory != NULL)
+    xfcemenu_tree_item_unref (header->directory);
+  header->directory = NULL;
+}
+
+static XfceMenuTreeAlias *
+xfcemenu_tree_alias_new (XfceMenuTreeDirectory *parent,
+		      XfceMenuTreeDirectory *directory,
+		      XfceMenuTreeItem      *item)
+{
+  XfceMenuTreeAlias *retval;
+
+  retval = g_new0 (XfceMenuTreeAlias, 1);
+
+  retval->item.type     = MATEMENU_TREE_ITEM_ALIAS;
+  retval->item.parent   = parent;
+  retval->item.refcount = 1;
+
+  retval->directory    = xfcemenu_tree_item_ref (directory);
+  if (item->type != MATEMENU_TREE_ITEM_ALIAS)
+    retval->aliased_item = xfcemenu_tree_item_ref (item);
+  else
+    retval->aliased_item = xfcemenu_tree_item_ref (xfcemenu_tree_alias_get_item (MATEMENU_TREE_ALIAS (item)));
+
+  xfcemenu_tree_item_set_parent (MATEMENU_TREE_ITEM (retval->directory), NULL);
+  xfcemenu_tree_item_set_parent (retval->aliased_item, NULL);
+
+  return retval;
+}
+
+static void
+xfcemenu_tree_alias_finalize (XfceMenuTreeAlias *alias)
+{
+  g_assert (alias->item.refcount == 0);
+
+  if (alias->directory != NULL)
+    xfcemenu_tree_item_unref (alias->directory);
+  alias->directory = NULL;
+
+  if (alias->aliased_item != NULL)
+    xfcemenu_tree_item_unref (alias->aliased_item);
+  alias->aliased_item = NULL;
+}
+
+static XfceMenuTreeEntry *
+xfcemenu_tree_entry_new (XfceMenuTreeDirectory *parent,
+		      DesktopEntry       *desktop_entry,
+		      const char         *desktop_file_id,
+		      gboolean            is_excluded,
+                      gboolean            is_nodisplay)
+{
+  XfceMenuTreeEntry *retval;
+
+  retval = g_new0 (XfceMenuTreeEntry, 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
+xfcemenu_tree_entry_finalize (XfceMenuTreeEntry *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
+xfcemenu_tree_entry_compare_by_id (XfceMenuTreeItem *a,
+				XfceMenuTreeItem *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 xfcemenu_tree_item_ref(gpointer itemp)
+{
+	XfceMenuTreeItem* item = (XfceMenuTreeItem*) itemp;
+
+	g_return_val_if_fail(item != NULL, NULL);
+	g_return_val_if_fail(item->refcount > 0, NULL);
+
+	item->refcount++;
+
+	return item;
+}
+
+void
+xfcemenu_tree_item_unref (gpointer itemp)
+{
+  XfceMenuTreeItem *item;
+
+  item = (XfceMenuTreeItem *) 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:
+	  xfcemenu_tree_directory_finalize (MATEMENU_TREE_DIRECTORY (item));
+	  break;
+
+	case MATEMENU_TREE_ITEM_ENTRY:
+	  xfcemenu_tree_entry_finalize (MATEMENU_TREE_ENTRY (item));
+	  break;
+
+	case MATEMENU_TREE_ITEM_SEPARATOR:
+	  break;
+
+	case MATEMENU_TREE_ITEM_HEADER:
+	  xfcemenu_tree_header_finalize (MATEMENU_TREE_HEADER (item));
+	  break;
+
+	case MATEMENU_TREE_ITEM_ALIAS:
+	  xfcemenu_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
+xfcemenu_tree_item_unref_and_unset_parent (gpointer itemp)
+{
+  XfceMenuTreeItem *item;
+
+  item = (XfceMenuTreeItem *) itemp;
+
+  g_return_if_fail (item != NULL);
+
+  xfcemenu_tree_item_set_parent (item, NULL);
+  xfcemenu_tree_item_unref (item);
+}
+
+void
+xfcemenu_tree_item_set_user_data (XfceMenuTreeItem  *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
+xfcemenu_tree_item_get_user_data (XfceMenuTreeItem *item)
+{
+  g_return_val_if_fail (item != NULL, NULL);
+
+  return item->user_data;
+}
+
+static inline const char *
+xfcemenu_tree_item_compare_get_name_helper (XfceMenuTreeItem    *item,
+					 XfceMenuTreeSortKey  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 = xfcemenu_tree_entry_get_display_name (MATEMENU_TREE_ENTRY (item));
+	  break;
+	default:
+	  g_assert_not_reached ();
+	  break;
+	}
+      break;
+
+    case MATEMENU_TREE_ITEM_ALIAS:
+      {
+        XfceMenuTreeItem *dir;
+        dir = MATEMENU_TREE_ITEM (MATEMENU_TREE_ALIAS (item)->directory);
+        name = xfcemenu_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
+xfcemenu_tree_item_compare (XfceMenuTreeItem *a,
+			 XfceMenuTreeItem *b,
+			 gpointer       sort_key_p)
+{
+  const char       *name_a;
+  const char       *name_b;
+  XfceMenuTreeSortKey  sort_key;
+
+  sort_key = GPOINTER_TO_INT (sort_key_p);
+
+  name_a = xfcemenu_tree_item_compare_get_name_helper (a, sort_key);
+  name_b = xfcemenu_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 (XfceMenuTree      *tree,
+			 GHashTable     *loaded_menu_files,
+                         MenuLayoutNode *where,
+                         MenuLayoutNode *from)
+{
+  MenuLayoutNode *insert_after;
+  MenuLayoutNode *menu_child;
+  MenuLayoutNode *from_child;
+
+  xfcemenu_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 (XfceMenuTree      *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)
+	    xfcemenu_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)
+    xfcemenu_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 (XfceMenuTree      *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 (XfceMenuTree      *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(XfceMenuTree* 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 (XfceMenuTree      *tree,
+		GHashTable     *loaded_menu_files,
+                const char     *dirname,
+                MenuLayoutNode *where)
+{
+  GDir       *dir;
+  const char *menu_file;
+
+  menu_verbose ("Loading merge dir \"%s\"\n", dirname);
+
+  xfcemenu_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 (XfceMenuTree      *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 (XfceMenuTree      *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 (XfceMenuTree      *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 (XfceMenuTree      *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 (XfceMenuTree      *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(XfceMenuTree* 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;
+}
+
+/* According to desktop spec, since our menu file is called 'mate-applications', our
+ * merged menu folders need to be called 'mate-applications-merged'.  We'll setup the folder
+ * 'applications-merged' if it doesn't exist yet, and a symlink pointing to it in the
+ * ~/.config/menus directory
+ */
+static void
+setup_merge_dir_symlink(void)
+{
+    gchar *user_config = (gchar *) g_get_user_config_dir();
+    gchar *merge_path = g_build_filename (user_config, "menus", "applications-merged", NULL);
+    GFile *merge_file = g_file_new_for_path (merge_path);
+    gchar *sym_path;
+    GFile *sym_file;
+
+    g_file_make_directory_with_parents (merge_file, NULL, NULL);
+
+    sym_path = g_build_filename (user_config, "menus", "mate-applications-merged", NULL);
+    sym_file = g_file_new_for_path (sym_path);
+    if (!g_file_query_exists (sym_file, NULL)) {
+        g_file_make_symbolic_link (sym_file, merge_path, NULL, NULL);
+    }
+
+    g_free (merge_path);
+    g_free (sym_path);
+    g_object_unref (merge_file);
+    g_object_unref (sym_file);
+}
+
+static void
+resolve_default_directory_dirs (XfceMenuTree      *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)
+    {
+		/* Parche para tomar las carpetas /mate/ */
+		char* path = g_build_filename(system_data_dirs[i], "mate", NULL);
+		before = add_directory_dir(tree, before, path);
+		g_free(path);
+		/* /fin parche */
+      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 (XfceMenuTree      *tree,
+			    GHashTable     *loaded_menu_files,
+                            MenuLayoutNode *layout)
+{
+  MenuLayoutNode     *root;
+  const char         *menu_name;
+  char               *merge_name;
+  const char * const *system_config_dirs;
+  int                 i;
+
+  setup_merge_dir_symlink();
+
+  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 (XfceMenuTree      *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 (XfceMenuTree      *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 (XfceMenuTree      *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
+xfcemenu_tree_resolve_files (XfceMenuTree      *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);
+
+          xfcemenu_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
+xfcemenu_tree_strip_duplicate_children (XfceMenuTree      *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)
+        xfcemenu_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
+xfcemenu_tree_execute_moves (XfceMenuTree      *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.
+           */
+          xfcemenu_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)
+    xfcemenu_tree_strip_duplicate_children (tree, layout);
+}
+
+static void
+xfcemenu_tree_load_layout (XfceMenuTree *tree)
+{
+  GHashTable *loaded_menu_files;
+  GError     *error;
+
+  if (tree->layout)
+    return;
+
+  if (!xfcemenu_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));
+  xfcemenu_tree_resolve_files (tree, loaded_menu_files, tree->layout);
+  g_hash_table_destroy (loaded_menu_files);
+
+  xfcemenu_tree_strip_duplicate_children (tree, tree->layout);
+  xfcemenu_tree_execute_moves (tree, tree->layout, NULL);
+}
+
+static void
+xfcemenu_tree_force_reload (XfceMenuTree *tree)
+{
+  xfcemenu_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,
+                         XfceMenuTreeDirectory *directory)
+{
+  directory->entries =
+    g_slist_prepend (directory->entries,
+		     xfcemenu_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,
+				  XfceMenuTreeDirectory *directory)
+{
+  directory->entries =
+    g_slist_prepend (directory->entries,
+		     xfcemenu_tree_entry_new (directory,
+					   desktop_entry,
+					   desktop_file_id,
+					   TRUE,
+                                           desktop_entry_get_no_display (desktop_entry)));
+}
+
+static void
+set_default_layout_values (XfceMenuTreeDirectory *parent,
+                           XfceMenuTreeDirectory *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)
+    {
+      XfceMenuTreeDirectory *subdir = tmp->data;
+
+      set_default_layout_values (child, subdir);
+
+      tmp = tmp->next;
+   }
+}
+
+static XfceMenuTreeDirectory *
+process_layout (XfceMenuTree          *tree,
+                XfceMenuTreeDirectory *parent,
+                MenuLayoutNode     *layout,
+                DesktopEntrySet    *allocated)
+{
+  MenuLayoutNode     *layout_iter;
+  XfceMenuTreeDirectory *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 = xfcemenu_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 */
+          {
+            XfceMenuTreeDirectory *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);
+      xfcemenu_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)
+    {
+      XfceMenuTreeDirectory *subdir = tmp->data;
+
+      set_default_layout_values (directory, subdir);
+
+      tmp = tmp->next;
+   }
+
+  tmp = directory->entries;
+  while (tmp != NULL)
+    {
+      XfceMenuTreeEntry *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);
+          xfcemenu_tree_item_unref_and_unset_parent (entry);
+        }
+
+      tmp = next;
+    }
+
+  g_assert (directory->name != NULL);
+
+  return directory;
+}
+
+static void
+process_only_unallocated (XfceMenuTree          *tree,
+			  XfceMenuTreeDirectory *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)
+        {
+          XfceMenuTreeEntry *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);
+              xfcemenu_tree_item_unref_and_unset_parent (entry);
+            }
+
+          tmp = next;
+        }
+    }
+
+  tmp = directory->subdirs;
+  while (tmp != NULL)
+    {
+      XfceMenuTreeDirectory *subdir = tmp->data;
+
+      process_only_unallocated (tree, subdir, allocated);
+
+      tmp = tmp->next;
+   }
+}
+
+static void preprocess_layout_info (XfceMenuTree          *tree,
+                                    XfceMenuTreeDirectory *directory);
+
+static GSList *
+get_layout_info (XfceMenuTreeDirectory *directory,
+                 gboolean           *is_default_layout)
+{
+  XfceMenuTreeDirectory *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 (XfceMenuTreeDirectory *directory)
+{
+  guint   len;
+  GSList *tmp;
+
+  len = 0;
+
+  tmp = directory->subdirs;
+  while (tmp != NULL)
+    {
+      XfceMenuTreeDirectory *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 (XfceMenuTree          *tree,
+                                      XfceMenuTreeDirectory *directory,
+                                      XfceMenuTreeDirectory *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)
+        {
+          XfceMenuTreeAlias *alias;
+          XfceMenuTreeItem  *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 ?
+                          xfcemenu_tree_entry_get_name (MATEMENU_TREE_ENTRY (item)) :
+                          (item->type == MATEMENU_TREE_ITEM_DIRECTORY ?
+                             xfcemenu_tree_directory_get_name (MATEMENU_TREE_DIRECTORY (item)) :
+                             xfcemenu_tree_directory_get_name (MATEMENU_TREE_ALIAS (item)->directory)),
+                        subdir->name);
+
+          alias = xfcemenu_tree_alias_new (directory, subdir, item);
+
+          g_slist_foreach (list,
+                           (GFunc) xfcemenu_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) xfcemenu_tree_item_set_parent,
+                               directory);
+              directory->subdirs = g_slist_concat (directory->subdirs,
+                                                   subdir->subdirs);
+              subdir->subdirs = NULL;
+
+              g_slist_foreach (subdir->entries,
+                               (GFunc) xfcemenu_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 (XfceMenuTree          *tree,
+                        XfceMenuTreeDirectory *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;
+      XfceMenuTreeDirectory *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);
+          xfcemenu_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)
+    {
+      XfceMenuTreeDirectory *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);
+          xfcemenu_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) xfcemenu_tree_entry_compare_by_id);
+      tmp = directory->entries;
+      while (tmp != NULL && tmp->next != NULL)
+        {
+          XfceMenuTreeItem *a = tmp->data;
+          XfceMenuTreeItem *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);
+              xfcemenu_tree_item_unref (b);
+            }
+          else
+            tmp = tmp->next;
+        }
+    }
+
+  directory->preprocessed = TRUE;
+}
+
+static void process_layout_info (XfceMenuTree          *tree,
+				 XfceMenuTreeDirectory *directory);
+
+static void
+check_pending_separator (XfceMenuTreeDirectory *directory)
+{
+  if (directory->layout_pending_separator)
+    {
+      menu_verbose ("Adding pending separator in '%s'\n", directory->name);
+
+      directory->contents = g_slist_append (directory->contents,
+					    xfcemenu_tree_separator_new (directory));
+      directory->layout_pending_separator = FALSE;
+    }
+}
+
+static void
+merge_alias (XfceMenuTree          *tree,
+	     XfceMenuTreeDirectory *directory,
+	     XfceMenuTreeAlias     *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,
+					xfcemenu_tree_item_ref (alias));
+}
+
+static void
+merge_subdir (XfceMenuTree          *tree,
+	      XfceMenuTreeDirectory *directory,
+	      XfceMenuTreeDirectory *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))
+    {
+      XfceMenuTreeHeader *header;
+
+      header = xfcemenu_tree_header_new (directory, subdir);
+      directory->contents = g_slist_append (directory->contents, header);
+
+      g_slist_foreach (subdir->contents,
+                       (GFunc) xfcemenu_tree_item_set_parent,
+                       directory);
+      directory->contents = g_slist_concat (directory->contents,
+                                            subdir->contents);
+      subdir->contents = NULL;
+      subdir->will_inline_header = G_MAXUINT16;
+
+      xfcemenu_tree_item_set_parent (MATEMENU_TREE_ITEM (subdir), NULL);
+    }
+  else
+    {
+      directory->contents = g_slist_append (directory->contents,
+					    xfcemenu_tree_item_ref (subdir));
+    }
+}
+
+static void
+merge_subdir_by_name (XfceMenuTree          *tree,
+		      XfceMenuTreeDirectory *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)
+    {
+      XfceMenuTreeDirectory *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);
+	  xfcemenu_tree_item_unref (subdir);
+	}
+
+      tmp = next;
+    }
+}
+
+static void
+merge_entry (XfceMenuTree          *tree,
+	     XfceMenuTreeDirectory *directory,
+	     XfceMenuTreeEntry     *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,
+					xfcemenu_tree_item_ref (entry));
+}
+
+static void
+merge_entry_by_id (XfceMenuTree          *tree,
+		   XfceMenuTreeDirectory *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)
+    {
+      XfceMenuTreeEntry *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);
+	  xfcemenu_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 (XfceMenuTree          *tree,
+	       XfceMenuTreeDirectory *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) xfcemenu_tree_item_compare,
+				     GINT_TO_POINTER (MATEMENU_TREE_SORT_NAME));
+
+  tmp = subdirs;
+  while (tmp != NULL)
+    {
+      XfceMenuTreeDirectory *subdir = tmp->data;
+
+      if (MATEMENU_TREE_ITEM (subdir)->type == MATEMENU_TREE_ITEM_ALIAS)
+        {
+	  merge_alias (tree, directory, MATEMENU_TREE_ALIAS (subdir));
+	  xfcemenu_tree_item_unref (subdir);
+        }
+      else if (!find_name_in_list (subdir->name, except))
+	{
+	  merge_subdir (tree, directory, subdir);
+	  xfcemenu_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 (XfceMenuTree          *tree,
+	       XfceMenuTreeDirectory *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) xfcemenu_tree_item_compare,
+				    GINT_TO_POINTER (tree->sort_key));
+
+  tmp = entries;
+  while (tmp != NULL)
+    {
+      XfceMenuTreeEntry *entry = tmp->data;
+
+      if (MATEMENU_TREE_ITEM (entry)->type == MATEMENU_TREE_ITEM_ALIAS)
+        {
+	  merge_alias (tree, directory, MATEMENU_TREE_ALIAS (entry));
+	  xfcemenu_tree_item_unref (entry);
+        }
+      else if (!find_name_in_list (entry->desktop_file_id, except))
+	{
+	  merge_entry (tree, directory, entry);
+	  xfcemenu_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 (XfceMenuTree          *tree,
+			   XfceMenuTreeDirectory *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) xfcemenu_tree_item_compare,
+				  GINT_TO_POINTER (tree->sort_key));
+
+  tmp = items;
+  while (tmp != NULL)
+    {
+      XfceMenuTreeItem     *item = tmp->data;
+      XfceMenuTreeItemType  type;
+
+      type = xfcemenu_tree_item_get_type (item);
+
+      if (type == MATEMENU_TREE_ITEM_ALIAS)
+        {
+          merge_alias (tree, directory, MATEMENU_TREE_ALIAS (item));
+          xfcemenu_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));
+	      xfcemenu_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));
+	      xfcemenu_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 (XfceMenuTree          *tree,
+		     XfceMenuTreeDirectory *directory)
+{
+  GSList *layout_info;
+
+  menu_verbose ("Processing menu layout hints for %s\n", directory->name);
+
+  g_slist_foreach (directory->contents,
+		   (GFunc) xfcemenu_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) xfcemenu_tree_item_unref,
+		   NULL);
+  g_slist_free (directory->subdirs);
+  directory->subdirs = NULL;
+
+  g_slist_foreach (directory->entries,
+		   (GFunc) xfcemenu_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,
+                        XfceMenuTree       *tree)
+{
+  if (tree->layout == layout)
+    {
+      xfcemenu_tree_force_rebuild (tree);
+      xfcemenu_tree_invoke_monitors (tree);
+    }
+}
+
+static void
+xfcemenu_tree_build_from_layout (XfceMenuTree *tree)
+{
+  DesktopEntrySet *allocated;
+
+  if (tree->root)
+    return;
+
+  xfcemenu_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)
+    {
+      xfcemenu_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
+xfcemenu_tree_force_rebuild (XfceMenuTree *tree)
+{
+  if (tree->root)
+    {
+      xfcemenu_tree_directory_set_tree (tree->root, NULL);
+      xfcemenu_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/src/xfcemenu-tree.h b/src/xfcemenu-tree.h
new file mode 100644
index 0000000..3b97d25
--- /dev/null
+++ b/src/xfcemenu-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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __MATEMENU_TREE_H__
+#define __MATEMENU_TREE_H__
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct XfceMenuTree          XfceMenuTree;
+typedef struct XfceMenuTreeItem      XfceMenuTreeItem;
+typedef struct XfceMenuTreeDirectory XfceMenuTreeDirectory;
+typedef struct XfceMenuTreeEntry     XfceMenuTreeEntry;
+typedef struct XfceMenuTreeSeparator XfceMenuTreeSeparator;
+typedef struct XfceMenuTreeHeader    XfceMenuTreeHeader;
+typedef struct XfceMenuTreeAlias     XfceMenuTreeAlias;
+
+typedef void (*XfceMenuTreeChangedFunc) (XfceMenuTree* 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
+} XfceMenuTreeItemType;
+
+#define MATEMENU_TREE_ITEM(i)      ((XfceMenuTreeItem*)(i))
+#define MATEMENU_TREE_DIRECTORY(i) ((XfceMenuTreeDirectory*)(i))
+#define MATEMENU_TREE_ENTRY(i)     ((XfceMenuTreeEntry*)(i))
+#define MATEMENU_TREE_SEPARATOR(i) ((XfceMenuTreeSeparator*)(i))
+#define MATEMENU_TREE_HEADER(i)    ((XfceMenuTreeHeader*)(i))
+#define MATEMENU_TREE_ALIAS(i)     ((XfceMenuTreeAlias*)(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
+} XfceMenuTreeFlags;
+
+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
+} XfceMenuTreeSortKey;
+
+XfceMenuTree* xfcemenu_tree_lookup(const char* menu_file, XfceMenuTreeFlags flags);
+
+XfceMenuTree* xfcemenu_tree_ref(XfceMenuTree* tree);
+void xfcemenu_tree_unref(XfceMenuTree* tree);
+
+void xfcemenu_tree_set_user_data(XfceMenuTree* tree, gpointer user_data, GDestroyNotify dnotify);
+gpointer xfcemenu_tree_get_user_data(XfceMenuTree* tree);
+
+const char* xfcemenu_tree_get_menu_file(XfceMenuTree* tree);
+XfceMenuTreeDirectory* xfcemenu_tree_get_root_directory(XfceMenuTree* tree);
+XfceMenuTreeDirectory* xfcemenu_tree_get_directory_from_path(XfceMenuTree* tree, const char* path);
+
+XfceMenuTreeSortKey xfcemenu_tree_get_sort_key(XfceMenuTree* tree);
+void xfcemenu_tree_set_sort_key(XfceMenuTree* tree, XfceMenuTreeSortKey sort_key);
+
+
+
+gpointer xfcemenu_tree_item_ref(gpointer item);
+void xfcemenu_tree_item_unref(gpointer item);
+
+void xfcemenu_tree_item_set_user_data(XfceMenuTreeItem* item, gpointer user_data, GDestroyNotify dnotify);
+gpointer xfcemenu_tree_item_get_user_data(XfceMenuTreeItem* item);
+
+XfceMenuTreeItemType xfcemenu_tree_item_get_type(XfceMenuTreeItem* item);
+XfceMenuTreeDirectory* xfcemenu_tree_item_get_parent(XfceMenuTreeItem* item);
+
+
+GSList* xfcemenu_tree_directory_get_contents(XfceMenuTreeDirectory* directory);
+const char* xfcemenu_tree_directory_get_name(XfceMenuTreeDirectory* directory);
+const char* xfcemenu_tree_directory_get_comment(XfceMenuTreeDirectory* directory);
+const char* xfcemenu_tree_directory_get_icon(XfceMenuTreeDirectory* directory);
+const char* xfcemenu_tree_directory_get_desktop_file_path(XfceMenuTreeDirectory* directory);
+const char* xfcemenu_tree_directory_get_menu_id(XfceMenuTreeDirectory* directory);
+XfceMenuTree* xfcemenu_tree_directory_get_tree(XfceMenuTreeDirectory* directory);
+
+gboolean xfcemenu_tree_directory_get_is_nodisplay(XfceMenuTreeDirectory* directory);
+
+char* xfcemenu_tree_directory_make_path(XfceMenuTreeDirectory* directory, XfceMenuTreeEntry* entry);
+
+
+const char* xfcemenu_tree_entry_get_name(XfceMenuTreeEntry* entry);
+const char* xfcemenu_tree_entry_get_generic_name(XfceMenuTreeEntry* entry);
+const char* xfcemenu_tree_entry_get_display_name(XfceMenuTreeEntry* entry);
+const char* xfcemenu_tree_entry_get_comment(XfceMenuTreeEntry* entry);
+const char* xfcemenu_tree_entry_get_icon(XfceMenuTreeEntry* entry);
+const char* xfcemenu_tree_entry_get_exec(XfceMenuTreeEntry* entry);
+gboolean xfcemenu_tree_entry_get_launch_in_terminal(XfceMenuTreeEntry* entry);
+const char* xfcemenu_tree_entry_get_desktop_file_path(XfceMenuTreeEntry* entry);
+const char* xfcemenu_tree_entry_get_desktop_file_id(XfceMenuTreeEntry* entry);
+gboolean xfcemenu_tree_entry_get_is_excluded(XfceMenuTreeEntry* entry);
+gboolean xfcemenu_tree_entry_get_is_nodisplay(XfceMenuTreeEntry* entry);
+
+XfceMenuTreeDirectory* xfcemenu_tree_header_get_directory(XfceMenuTreeHeader* header);
+
+XfceMenuTreeDirectory* xfcemenu_tree_alias_get_directory(XfceMenuTreeAlias* alias);
+XfceMenuTreeItem* xfcemenu_tree_alias_get_item(XfceMenuTreeAlias* alias);
+
+void xfcemenu_tree_add_monitor(XfceMenuTree* tree, XfceMenuTreeChangedFunc callback, gpointer user_data);
+void xfcemenu_tree_remove_monitor(XfceMenuTree* tree, XfceMenuTreeChangedFunc callback, gpointer user_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MATEMENU_TREE_H__ */

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the Xfce4-commits mailing list