[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