Index: configure.in
===================================================================
RCS file: /cvs/gnome/gnome-panel/configure.in,v
retrieving revision 1.629
diff -u -u -r1.629 configure.in
--- configure.in	28 Oct 2005 15:46:25 -0000	1.629
+++ configure.in	29 Oct 2005 07:39:52 -0000
@@ -215,6 +215,93 @@
 fi
 AC_SUBST(REBUILD)
 
+#############################
+# X, Xau, Xmu
+#############################
+#
+# If Pango included the shared library dependencies from X11 in
+# the pkg-config output, then we use that (to avoid duplicates).
+# but if they were omitted to avoid binary compatibility problems
+# then we need to repeat the checks.
+#
+if $PKG_CONFIG --exists pangoxft ; then
+  PANGO_PACKAGES="pangox pangoxft"
+else
+  PANGO_PACKAGES="pangox"
+fi
+
+x_libs="`$PKG_CONFIG --libs $PANGO_PACKAGES`"
+case x_libs in
+  *-lX11*) pango_omitted_x_deps=no ;;
+  *)       pango_omitted_x_deps=yes ;;
+esac
+
+if test $pango_omitted_x_deps = yes ; then
+  AC_PATH_XTRA
+
+  if test x$no_x = xyes ; then
+    AC_MSG_ERROR([X development libraries not found])
+  else
+    X_LIBS="$X_PRE_LIBS $X_LIBS -lX11 $X_EXTRA_LIBS"
+  fi
+fi
+
+fusa_save_LDFLAGS=$LDFLAGS
+LDFLAGS="$LDFLAGS $X_LIBS"
+AC_CHECK_LIB(Xau, XauFileName,
+	     X_LIBS="$X_LIBS -lXau",
+	     [AC_MSG_ERROR([Xau development libraries not found])])
+AC_CHECK_LIB(Xmu, XmuClientWindow,
+	     X_LIBS="$X_LIBS -lXmu",
+	     [AC_MSG_ERROR([Xmu development libraries not found])])
+LDFLAGS=$panel_save_LDFLAGS
+
+##############################
+# Custom Junk
+##############################
+
+AC_DEFUN([AC_DEFINE_PATH], [
+  test "x$prefix" = xNONE && prefix="$ac_default_prefix"
+  test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+  ac_define_path=`eval echo [$]$2`
+  ac_define_path=`eval echo [$]ac_define_path`
+  $1="$ac_define_path"
+  AC_SUBST($1)
+  ifelse($3, ,
+    AC_DEFINE_UNQUOTED($1, "$ac_define_path"),
+    AC_DEFINE_UNQUOTED($1, "$ac_define_path", $3))
+])
+
+##############################
+# GDM Config File
+##############################
+
+GDMCONFIGFILE=
+
+AC_ARG_WITH([gdm_config],
+	    AC_HELP_STRING([--with-gdm-config=file],
+			   [The location of the GDM configuration file]),
+	    [gdmconfigfile="$withval"])
+
+if test "x$gdmconfigfile" == "x" -o "x$gdmconfigfile" == "xno"; then
+	AC_MSG_CHECKING([for gdm.conf])
+
+	if test -f "/etc/gdm/gdm.conf"; then
+		gdmconfigfile="/etc/gdm/gdm.conf"
+	elif test -f "/etc/X11/gdm/gdm.conf"; then
+		gdmconfigfile="/etc/X11/gdm/gdm.conf"
+	else
+		AC_MSG_ERROR([The GDM config file could not be found, please specify it manually])
+	fi
+
+	AC_MSG_RESULT([$gdmconfigfile])
+else
+	AC_MSG_RESULT([using "$gdmconfigfile" as the gdm.conf location])
+fi
+
+AC_DEFINE_PATH(GDMCONFIGFILE, "$gdmconfigfile", [The location of the GDM configuration file])
+
+
 AC_OUTPUT([
 Makefile
 gnome-panel.spec
Index: gnome-panel/Makefile.am
===================================================================
RCS file: /cvs/gnome/gnome-panel/gnome-panel/Makefile.am,v
retrieving revision 1.350
diff -u -u -r1.350 Makefile.am
--- gnome-panel/Makefile.am	11 Apr 2005 14:58:40 -0000	1.350
+++ gnome-panel/Makefile.am	29 Oct 2005 07:39:52 -0000
@@ -57,6 +57,23 @@
 	egg-recent-view-gtk.h	\
 	egg-recent-util.h
 
+FUSA_C =			\
+	fusa-display.c		\
+	fusa-manager.c		\
+	fusa-user.c		\
+	fusa-user-menu-item.c	\
+	fusa-utils.c		\
+	gdmcomm.c
+
+FUSA_H =			\
+	fusa-display.h		\
+	fusa-display-private.h	\
+	fusa-manager.h		\
+	fusa-user.h		\
+	fusa-user-menu-item.h	\
+	fusa-utils.h		\
+	gdmcomm.h
+
 EGG_ACCELERATORS_FILES =        \
 	eggaccelerators.c       \
 	eggaccelerators.h       \
@@ -114,7 +131,9 @@
 	panel-force-quit.c	\
 	panel-lockdown.c	\
 	panel-addto.c		\
+	panel-switch-to.c	\
 	$(EGG_RECENT_FILES_C)	\
+	$(FUSA_C)		\
 	$(NULL)
 
 panel_headers =			\
@@ -161,6 +180,7 @@
 	panel-lockdown.h	\
 	panel-addto.h		\
 	$(EGG_RECENT_FILES_H)	\
+	$(FUSA_H)		\
 	$(NULL)
 
 gnome_panel_SOURCES =			\
Index: gnome-panel/fusa-display-private.h
===================================================================
RCS file: gnome-panel/fusa-display-private.h
diff -N gnome-panel/fusa-display-private.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/fusa-display-private.h	29 Oct 2005 07:39:52 -0000
@@ -0,0 +1,42 @@
+/* 
+ * Fast User Switch Applet: fusa-display-private.h
+ * 
+ * Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * Private Interfaces to the FusaDisplay object
+ */
+
+#ifndef __FUSA_DISPLAY_PRIVATE_H__
+#define __FUSA_DISPLAY_PRIVATE_H__ 1
+
+#include "fusa-manager.h"
+
+G_BEGIN_DECLS
+
+FusaDisplay *_fusa_display_new      (FusaManager *manager,
+				     const gchar *name,
+				     FusaUser    *user,
+				     gint         console,
+				     gboolean     nested);
+void         _fusa_display_set_user (FusaDisplay *display,
+				     FusaUser    *user);
+
+G_END_DECLS
+
+#endif /* !__FUSA_DISPLAY_PRIVATE_H__ */
Index: gnome-panel/fusa-display.c
===================================================================
RCS file: gnome-panel/fusa-display.c
diff -N gnome-panel/fusa-display.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/fusa-display.c	29 Oct 2005 07:39:52 -0000
@@ -0,0 +1,388 @@
+/*
+ * Fast User Switch Applet: fusa-display.c
+ * 
+ * Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include "fusa-user-private.h"
+#include "fusa-manager.h"
+
+#include "fusa-display-private.h"
+
+/* **************** *
+ *  Private Macros  *
+ * **************** */
+ 
+#define FUSA_DISPLAY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), FUSA_TYPE_DISPLAY, FusaDisplayClass))
+#define FUSA_IS_DISPLAY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), FUSA_TYPE_DISPLAY))
+#define FUSA_DISPLAY_GET_CLASS(object) \
+  (G_TYPE_INSTANCE_GET_CLASS ((object), FUSA_TYPE_DISPLAY, FusaDisplayClass))
+
+
+/* ********************** *
+ *  Private Enumerations  *
+ * ********************** */
+
+enum
+{
+  PROP_0,
+  PROP_MANAGER,
+  PROP_NAME,
+  PROP_USER,
+  PROP_CONSOLE,
+  PROP_NESTED
+};
+
+
+/* ******************** *
+ *  Private Structures  *
+ * ******************** */
+
+struct _FusaDisplay
+{
+  GObject parent;
+
+  FusaManager *manager;
+  gchar *name;
+  FusaUser *user;
+  gint console;
+  guint8 nested : 1;
+};
+
+typedef struct _FusaDisplayClass
+{
+  GObjectClass parent_class;
+}
+FusaDisplayClass;
+
+
+/* ********************* *
+ *  Function Prototypes  *
+ * ********************* */
+
+/* GObject Functions */
+static void fusa_display_set_property (GObject      *object,
+				       guint         param_id,
+				       const GValue *value,
+				       GParamSpec   *pspec);
+static void fusa_display_get_property (GObject      *object,
+				       guint         param_id,
+				       GValue       *value,
+				       GParamSpec   *pspec);
+static void fusa_display_finalize     (GObject      *object);
+
+
+/* ******************* *
+ *  GType Declaration  *
+ * ******************* */
+
+G_DEFINE_TYPE (FusaDisplay, fusa_display, G_TYPE_OBJECT);
+
+
+/* ********************** *
+ *  GType Initialization  *
+ * ********************** */
+
+static void
+fusa_display_class_init (FusaDisplayClass *class)
+{
+  GObjectClass *gobject_class;
+
+  gobject_class = G_OBJECT_CLASS (class);
+
+  gobject_class->set_property = fusa_display_set_property;
+  gobject_class->get_property = fusa_display_get_property;
+  gobject_class->finalize = fusa_display_finalize;
+
+  g_object_class_install_property (gobject_class,
+				   PROP_MANAGER,
+				   g_param_spec_object ("manager",
+							_("Manager"),
+							_("The manager which owns this object."),
+							FUSA_TYPE_MANAGER,
+							(G_PARAM_READWRITE |
+							 G_PARAM_CONSTRUCT_ONLY)));
+  g_object_class_install_property (gobject_class,
+				   PROP_NAME,
+				   g_param_spec_string ("name",
+							_("Name"),
+							_("The name of the X11 display this object refers to."),
+							NULL,
+							G_PARAM_READABLE));
+  g_object_class_install_property (gobject_class,
+				   PROP_USER,
+				   g_param_spec_object ("user",
+							_("User"),
+							_("The user currently logged in on this virtual terminal."),
+							FUSA_TYPE_USER,
+							G_PARAM_READABLE));
+  g_object_class_install_property (gobject_class,
+				   PROP_CONSOLE,
+				   g_param_spec_int ("console",
+						     _("Console"),
+						     _("The number of the virtual console this display can be found on, or %-1."),
+						     -1, G_MAXINT, -1,
+						     G_PARAM_READABLE));
+  g_object_class_install_property (gobject_class,
+				   PROP_NESTED,
+				   g_param_spec_boolean ("nested",
+							 _("Nested"),
+							 _("Whether or not this display is a windowed (Xnest) display."),
+							 FALSE,
+							 G_PARAM_READABLE));
+}
+
+static void
+fusa_display_init (FusaDisplay *display)
+{
+  display->console = -1;
+}
+
+
+/* ******************* *
+ *  GObject Functions  *
+ * ******************* */
+
+static void
+fusa_display_set_property (GObject      *object,
+			   guint         param_id,
+			   const GValue *value,
+			   GParamSpec   *pspec)
+{
+  FusaDisplay *display;
+
+  display = FUSA_DISPLAY (object);
+
+  switch (param_id)
+    {
+    case PROP_MANAGER:
+      display->manager = g_value_get_object (value);
+      g_object_add_weak_pointer (G_OBJECT (display->manager),
+				 (gpointer *) &display->manager);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    }
+}
+
+static void
+fusa_display_get_property (GObject    *object,
+			   guint       param_id,
+			   GValue     *value,
+			   GParamSpec *pspec)
+{
+  FusaDisplay *display;
+
+  display = FUSA_DISPLAY (object);
+
+  switch (param_id)
+    {
+    case PROP_NAME:
+      g_value_set_string (value, display->name);
+      break;
+    case PROP_USER:
+      g_value_set_object (value, display->user);
+      break;
+    case PROP_CONSOLE:
+      g_value_set_int (value, display->console);
+      break;
+    case PROP_NESTED:
+      g_value_set_boolean (value, display->nested);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    }
+}
+
+static void
+fusa_display_finalize (GObject *object)
+{
+  FusaDisplay *display;
+
+  display = FUSA_DISPLAY (object);
+
+  _fusa_display_set_user (display, NULL);
+  g_free (display->name);
+  g_object_remove_weak_pointer (G_OBJECT (display->manager),
+				(gpointer *) &display->manager);
+
+  if (G_OBJECT_CLASS (fusa_display_parent_class)->finalize)
+    (*G_OBJECT_CLASS (fusa_display_parent_class)->finalize) (object);
+}
+
+
+/* ******************* *
+ *  Utility Functions  *
+ * ******************* */
+
+static void
+user_weak_notify (gpointer  data,
+		  GObject  *user_ptr)
+{
+  FusaDisplay *display;
+
+  display = FUSA_DISPLAY (data);
+
+  display->user = NULL;
+  g_object_notify (G_OBJECT (data), "user");
+}
+
+
+/* ************************************************************************** *
+ *  Semi-Private API                                                          *
+ * ************************************************************************** */
+
+FusaDisplay *
+_fusa_display_new (FusaManager *manager,
+		   const gchar *name,
+		   FusaUser    *user,
+		   gint         console,
+		   gboolean     nested)
+{
+  FusaDisplay *display;
+
+  display = g_object_new (FUSA_TYPE_DISPLAY, "manager", manager, NULL);
+  display->name = g_strdup (name);
+  _fusa_display_set_user (display, user);
+  display->console = console;
+  display->nested = !!nested;
+
+  return display;
+}
+
+/**
+ * _fusa_display_set_user:
+ * @display: the virtual terminal object to modify.
+ * @user: the user logged in on the terminal, or %NULL
+ * 
+ * Updates @display to know who (if anyone) is logged in on the session it
+ * represents.
+ * 
+ * Since: 1.0
+ **/
+void
+_fusa_display_set_user (FusaDisplay *display,
+			FusaUser    *user)
+{
+  g_return_if_fail (FUSA_IS_DISPLAY (display));
+  g_return_if_fail (user == NULL || FUSA_IS_USER (user));
+
+  if (display->user == user)
+    return;
+
+  if (display->user)
+    {
+      g_object_weak_unref (G_OBJECT (display->user), user_weak_notify, display);
+      _fusa_user_remove_display (display->user, display);
+    }
+
+  display->user = user;
+
+  if (user)
+    {
+      g_object_weak_ref (G_OBJECT (display->user), user_weak_notify, display);
+      _fusa_user_add_display (user, display);
+    }
+
+  g_object_notify (G_OBJECT (display), "user");
+}
+
+
+/* ************************************************************************** *
+ *  Public API                                                                *
+ * ************************************************************************** */
+
+/**
+ * fusa_display_get_name:
+ * @display: the display object to examine.
+ * 
+ * Retrieves the name for the X11 session on @display, e.g. ":0".
+ * 
+ * Returns: a constant character array which should not be modified or freed.
+ * 
+ * Since: 1.0
+ **/
+G_CONST_RETURN gchar *
+fusa_display_get_name (FusaDisplay *display)
+{
+  g_return_val_if_fail (FUSA_IS_DISPLAY (display), NULL);
+
+  return display->name;
+}
+
+/**
+ * fusa_display_get_user:
+ * @display: the virtual terminal object to examine.
+ * 
+ * Retrieves the user currently logged in to the virtual terminal @display.
+ * 
+ * Returns: a pointer to a #FusaUser object which should not be unreferenced.
+ * 
+ * Since: 1.0
+ **/
+FusaUser *
+fusa_display_get_user (FusaDisplay *display)
+{
+  g_return_val_if_fail (FUSA_IS_DISPLAY (display), NULL);
+
+  return display->user;
+}
+
+/**
+ * fusa_display_get_console:
+ * @display: the display to examine.
+ * 
+ * Retrieves the numeric ID of the virtual console used by @display. If
+ * @display represents a nested session, this will be the console that @display
+ * exists on.
+ * 
+ * Returns: an integer.
+ * 
+ * Since: 1.0
+ **/
+gint
+fusa_display_get_console (FusaDisplay *display)
+{
+  g_return_val_if_fail (FUSA_IS_DISPLAY (display), -1);
+
+  return display->console;
+}
+
+/**
+ * fusa_display_get_nested:
+ * @display: the display to examine.
+ * 
+ * Retrieves whether or not @display is a nested (Xnest) session.
+ * 
+ * Returns: a boolean value.
+ * 
+ * Since: 1.0
+ **/
+gboolean
+fusa_display_get_nested (FusaDisplay *display)
+{
+  g_return_val_if_fail (FUSA_IS_DISPLAY (display), FALSE);
+
+  return display->nested;
+}
Index: gnome-panel/fusa-display.h
===================================================================
RCS file: gnome-panel/fusa-display.h
diff -N gnome-panel/fusa-display.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/fusa-display.h	29 Oct 2005 07:39:52 -0000
@@ -0,0 +1,53 @@
+/*
+ * Fast User Switch Applet: fusa-display.h
+ * 
+ * Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* 
+ * Facade object used to represent an open VT or Xnest window, managed by FusaDisplayManager
+ */
+
+#ifndef __FUSA_DISPLAY__
+#define __FUSA_DISPLAY__ 1
+
+#include "fusa-user.h"
+
+G_BEGIN_DECLS
+
+#define FUSA_TYPE_DISPLAY \
+  (fusa_display_get_type ())
+#define FUSA_DISPLAY(object) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((object), FUSA_TYPE_DISPLAY, FusaDisplay))
+#define FUSA_IS_DISPLAY(object) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((object), FUSA_TYPE_DISPLAY))
+
+typedef struct _FusaDisplay FusaDisplay;
+
+GType                 fusa_display_get_type      (void) G_GNUC_CONST;
+
+G_CONST_RETURN gchar *fusa_display_get_name      (FusaDisplay *display);
+FusaUser             *fusa_display_get_user      (FusaDisplay *display);
+gint                  fusa_display_get_console   (FusaDisplay *display);
+gboolean              fusa_display_get_nested    (FusaDisplay *display);
+
+void                  fusa_display_switch_to     (FusaDisplay *display,
+						  GdkScreen   *screen);
+
+G_END_DECLS
+
+#endif /* !__FUSA_DISPLAY__ */
Index: gnome-panel/fusa-manager-private.h
===================================================================
RCS file: gnome-panel/fusa-manager-private.h
diff -N gnome-panel/fusa-manager-private.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/fusa-manager-private.h	29 Oct 2005 07:39:52 -0000
@@ -0,0 +1,44 @@
+/* 
+ * Fast User-Switching Applet: fusa-manager-private.h
+ * 
+ * Copyright (C) 2004 James M. Cape <jcape@ignore-your.tv>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * Private interfaces to the FusaManager object
+ */
+
+#ifndef __FUSA_MANAGER_PRIVATE_H__
+#define __FUSA_MANAGER_PRIVATE_H__ 1
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "fusa-manager.h"
+
+G_BEGIN_DECLS
+
+void       _fusa_manager_set_display (FusaManager *manager,
+				      FusaDisplay *display,
+				      GdkScreen   *screen);
+GdkPixbuf *_fusa_manager_render_icon (FusaManager *manager,
+				      FusaUser    *user,
+				      GtkWidget   *widget,
+				      gint         icon_size);
+
+G_END_DECLS
+
+#endif /* !__FUSA_MANAGER_PRIVATE_H__ */
Index: gnome-panel/fusa-manager.c
===================================================================
RCS file: gnome-panel/fusa-manager.c
diff -N gnome-panel/fusa-manager.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/fusa-manager.c	29 Oct 2005 07:39:52 -0000
@@ -0,0 +1,2171 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 2 -*-
+ * 
+ * Fast User Switch Applet: fusa-manager.c
+ * 
+ * Copyright (C) 2005 James M. Cape <jcape@ignore-your.tv>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <pwd.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <X11/Xauth.h>
+#include <X11/Xlib.h>
+#include <X11/Xmu/WinUtil.h>
+
+#include <gdk/gdkx.h>
+
+#include <gtk/gtkicontheme.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkstock.h>
+
+#include <libgnomevfs/gnome-vfs-ops.h>
+
+#include "gdmcomm.h"
+#include "fusa-utils.h"
+#include "fusa-display-private.h"
+#include "fusa-user-private.h"
+
+#include "fusa-manager-private.h"
+
+
+/* **************** *
+ *  Private Macros  *
+ * **************** */
+
+#define FUSA_MANAGER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), FUSA_TYPE_MANAGER, FusaManagerClass))
+#define FUSA_IS_MANAGER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), FUSA_TYPE_MANAGER))
+#define FUSA_MANAGER_GET_CLASS(object) \
+  (G_TYPE_INSTANCE_GET_CLASS ((object), FUSA_TYPE_MANAGER, FusaManagerClass))
+
+/* Prefs Defaults */
+#define DEFAULT_ALLOW_ROOT	TRUE
+#define DEFAULT_MAX_ICON_SIZE	128
+#define DEFAULT_USER_MAX_FILE	65536
+#define DEFAULT_MINIMAL_UID	100
+#define DEFAULT_GLOBAL_FACE_DIR	DATADIR "/faces"
+#define DEFAULT_USER_ICON	"stock_person"
+#define DEFAULT_EXCLUDE		{ "bin",	\
+				  "daemon",	\
+				  "adm",	\
+				  "lp",		\
+				  "sync",	\
+				  "shutdown",	\
+				  "halt",	\
+				  "mail",	\
+				  "news",	\
+				  "uucp",	\
+				  "operator",	\
+				  "nobody",	\
+				  "gdm",	\
+				  "postgres",	\
+				  "pvm",	\
+				  "rpm",	\
+				  "nfsnobody",	\
+				  "pcap",	\
+				  NULL }
+
+/* Magic */
+#define MINIMAL_GDM_VERSION	"2.6.0.1"
+
+
+/* ********************** *
+ *  Private Enumerations  *
+ * ********************** */
+
+enum
+{
+  USER_ADDED,
+  USER_REMOVED,
+  DISPLAY_ADDED,
+  DISPLAY_REMOVED,
+  LAST_SIGNAL
+};
+
+typedef enum
+{
+  OP_UPDATE_DISPLAYS,
+  OP_NEW_CONSOLE,
+  OP_NEW_XNEST,
+  OP_ACTIVATE_DISPLAY
+}
+OpType;
+
+typedef enum
+{
+  OP_RESULT_DISPLAY_UPDATES,
+  OP_RESULT_DISPLAY_ACTIVATED,
+  OP_RESULT_NEW_DISPLAY,
+  OP_RESULT_ERROR
+}
+OpResultType;
+
+
+/* ******************** *
+ *  Private Structures  *
+ * ******************** */
+
+/* Class Structures */
+struct _FusaManager
+{
+  GObject parent;
+
+  /* Child Objects */
+  GHashTable *displays;
+  GHashTable *displays_by_console;
+  GHashTable *users;
+  GHashTable *users_by_uid;
+
+  /* Updaters */
+  GnomeVFSMonitorHandle *passwd_monitor;
+  GnomeVFSMonitorHandle *gdmconfig_monitor;
+  GnomeVFSMonitorHandle *shells_monitor;
+  GAsyncQueue *results_q;
+  guint results_pull_id;
+  guint update_displays_id;
+
+  /* GDM Config */
+  GHashTable *shells;
+  GHashTable *exclusions;
+  gchar *global_face_dir;
+  uid_t minimal_uid;
+  gsize user_max_file;
+  gint max_icon_size;
+
+  guint8 allow_root  : 1;
+  guint8 relax_group : 1;
+  guint8 relax_other : 1;
+};
+
+typedef struct _FusaManagerClass
+{
+  GObjectClass parent_class;
+
+  /* Signals */
+  void (*user_added)        (FusaManager *manager,
+			     FusaUser    *user);
+  void (*user_removed)      (FusaManager *manager,
+			     FusaUser    *user);
+  void (*display_added)     (FusaManager *manager,
+			     FusaDisplay *display);
+  void (*display_removed)   (FusaManager *manager,
+			     FusaDisplay *display);
+}
+FusaManagerClass;
+
+/* Thread Back-n-Forth Structures */
+typedef struct _DisplayUpdate
+{
+  gchar *name;
+  gchar *username;
+  gchar *location;
+}
+DisplayUpdate;
+
+typedef struct _DisplayClosure
+{
+  FusaManagerDisplayCallback func;
+  gpointer data;
+  GDestroyNotify notify;
+}
+DisplayClosure;
+
+typedef struct _Op
+{
+  FusaManager *manager;
+  FusaDisplay *display;
+  GdkScreen *screen;
+  gint console;
+  DisplayClosure *closure;
+
+  OpType type:3;
+}
+Op;
+
+typedef union _OpResult
+{
+  OpResultType type;
+
+  struct {
+    OpResultType type;
+    DisplayUpdate *items;
+    guint n_items;
+  } updates;
+
+  struct {
+    OpResultType type;
+    DisplayClosure *closure;
+    gchar *name;
+  } new;
+
+  struct {
+    OpResultType type;
+    DisplayClosure *closure;
+    FusaDisplay *display;
+  } activate;
+
+  struct {
+    OpResultType type;
+    DisplayClosure *closure;
+    GError *error;
+  } error;
+}
+OpResult;
+
+typedef struct _CleanDisplaysData
+{
+  FusaManager *manager;
+  GSList *displays;
+}
+CleanDisplaysData;
+
+
+/* ********************* *
+ *  Function Prototypes  *
+ * ********************* */
+
+/* GObject Functions */
+static void fusa_manager_finalize (GObject *object);
+
+/* GnomeVFSMonitor Callbacks */
+static void gdmconfig_monitor_cb (GnomeVFSMonitorHandle    *handle,
+				  const gchar              *text_uri,
+				  const gchar              *info_uri,
+				  GnomeVFSMonitorEventType  event_type,
+				  gpointer                  user_data);
+static void shells_monitor_cb    (GnomeVFSMonitorHandle    *handle,
+				  const gchar              *text_uri,
+				  const gchar              *info_uri,
+				  GnomeVFSMonitorEventType  event_type,
+				  gpointer                  user_data);
+static void passwd_monitor_cb    (GnomeVFSMonitorHandle    *handle,
+				  const gchar              *text_uri,
+				  const gchar              *info_uri,
+				  GnomeVFSMonitorEventType  event_type,
+				  gpointer                  user_data);
+
+/* User Functions */
+static gboolean        check_user_file                (const gchar  *filename,
+						       uid_t         uid,
+						       gssize        max_file_size,
+						       gboolean      relax_group,
+						       gboolean      relax_other);
+static void            emit_user_icon_changed_signals (gpointer      key,
+						       gpointer      value,
+						       gpointer      user_data);
+static inline gboolean strv_equals_string_table       (gchar       **strv,
+						       GHashTable   *table);
+static void            reload_gdm_config              (FusaManager  *manager,
+						       gboolean     *users_dirty,
+						       gboolean     *icons_dirty);
+static void            reload_shells                  (FusaManager  *manager);
+static void            reload_passwd                  (FusaManager  *manager);
+
+/* Displays - Main Thread Functions */
+static void        op_result_free            (OpResult                   *result);
+static inline void process_display_updates   (FusaManager                *manager,
+					      OpResult                   *result);
+static gboolean    results_pull_func         (gpointer                    data);
+static gboolean    push_update_displays_func (gpointer                    data);
+static void        push_dm_op                (FusaManager                *manager,
+					      OpType                      type,
+					      GdkScreen                  *screen,
+					      FusaDisplay                *display,
+					      FusaManagerDisplayCallback  func,
+					      gpointer                    data,
+					      GDestroyNotify              notify);
+
+/* Displays - IO Thread Functions */
+static void        push_new_result       (FusaManager *manager,
+					  Op          *op,
+					  const gchar *name);
+static void        push_error_result     (FusaManager *manager,
+					  Op          *op,
+					  const gchar *result);
+static gboolean    dm_op_update_displays (FusaManager *manager);
+static inline void dm_op_new_console     (FusaManager *manager,
+					  Op          *op);
+static inline void dm_op_new_xnest       (FusaManager *manager,
+					  Op          *op);
+static gpointer    dm_op_threadfunc      (gpointer     data);
+
+/* Utility Functions */
+static void         listify_hash_values_hfunc (gpointer      key,
+					       gpointer      value,
+					       gpointer      data);
+static gboolean     clear_hash_table_hrfunc   (gpointer      key,
+					       gpointer      value,
+					       gpointer      data);
+static FusaDisplay *real_get_display          (FusaManager  *manager,
+					       const gchar  *name);
+
+
+/* ****************** *
+ *  Global Variables  *
+ * ****************** */
+
+static guint        signals[LAST_SIGNAL] = { 0 };
+static gpointer     default_manager = NULL;
+static GThread     *dm_op_thread = NULL;
+static GAsyncQueue *op_q = NULL;
+
+
+/* ******************* *
+ *  GType Declaration  *
+ * ******************* */
+
+G_DEFINE_TYPE (FusaManager, fusa_manager, G_TYPE_OBJECT);
+
+
+/* ********************** *
+ *  GType Initialization  *
+ * ********************** */
+
+static void
+fusa_manager_class_init (FusaManagerClass *class)
+{
+  GObjectClass *gobject_class;
+  GError *err;
+
+  gobject_class = G_OBJECT_CLASS (class);
+
+  gobject_class->finalize = fusa_manager_finalize;
+
+  signals[USER_ADDED] =
+    g_signal_new ("user-added",
+		  G_TYPE_FROM_CLASS (class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (FusaManagerClass, user_added),
+		  NULL, NULL,
+		  g_cclosure_marshal_VOID__OBJECT,
+		  G_TYPE_NONE, 1, FUSA_TYPE_USER);
+  signals[USER_REMOVED] =
+    g_signal_new ("user-removed",
+		  G_TYPE_FROM_CLASS (class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (FusaManagerClass, user_removed),
+		  NULL, NULL,
+		  g_cclosure_marshal_VOID__OBJECT,
+		  G_TYPE_NONE, 1, FUSA_TYPE_USER);
+  signals[DISPLAY_ADDED] =
+    g_signal_new ("display-added",
+		  G_TYPE_FROM_CLASS (class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (FusaManagerClass, display_added),
+		  NULL, NULL,
+		  g_cclosure_marshal_VOID__OBJECT,
+		  G_TYPE_NONE, 1, FUSA_TYPE_DISPLAY);
+  signals[DISPLAY_REMOVED] =
+    g_signal_new ("display-removed",
+		  G_TYPE_FROM_CLASS (class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (FusaManagerClass, display_removed),
+		  NULL, NULL,
+		  g_cclosure_marshal_VOID__OBJECT,
+		  G_TYPE_NONE, 1, FUSA_TYPE_DISPLAY);
+
+  /* Classwide Variables */
+  op_q = g_async_queue_new ();
+  err = NULL;
+  dm_op_thread = g_thread_create (dm_op_threadfunc, NULL, FALSE, &err);
+  if (!dm_op_thread)
+    {
+      g_error ("Could not create a thread to contact the display manager: %s",
+	       err->message);
+      exit (-1);
+    }
+}
+
+static void
+fusa_manager_init (FusaManager *manager)
+{
+  GError *error;
+  gchar *uri;
+  GnomeVFSResult result;
+
+  /* GDM config file */
+  manager->exclusions = g_hash_table_new_full (g_str_hash, g_str_equal,
+					       g_free, NULL);
+  reload_gdm_config (manager, NULL, NULL);
+  error = NULL;
+  uri = g_filename_to_uri (GDMCONFIGFILE, NULL, &error);
+  if (!uri)
+    {
+      g_critical ("Could not create URI for GDM configuration file `%s': %s",
+		  GDMCONFIGFILE, error->message);
+      g_error_free (error);
+    }
+  else
+    {
+      result = gnome_vfs_monitor_add (&(manager->gdmconfig_monitor), uri,
+				      GNOME_VFS_MONITOR_FILE,
+				      gdmconfig_monitor_cb, manager);
+      g_free (uri);
+
+      if (result != GNOME_VFS_OK)
+	g_critical ("Could not install monitor for GDM configuration file `%s': %s",
+		    GDMCONFIGFILE, gnome_vfs_result_to_string (result));
+    }
+
+  /* /etc/shells */
+  manager->shells = g_hash_table_new_full (g_str_hash, g_str_equal,
+					   g_free, NULL);
+  reload_shells (manager);
+  error = NULL;
+  uri = g_filename_to_uri ("/etc/shells", NULL, &error);
+  if (!uri)
+    {
+      g_critical ("Could not create URI for shells file `%s': %s",
+		  GDMCONFIGFILE, error->message);
+      g_error_free (error);
+    }
+  else
+    {
+      result = gnome_vfs_monitor_add (&(manager->shells_monitor), uri,
+				      GNOME_VFS_MONITOR_FILE,
+				      shells_monitor_cb, manager);
+      g_free (uri);
+
+      if (result != GNOME_VFS_OK)
+	g_critical ("Could not install monitor for shells file `%s': %s",
+		    GDMCONFIGFILE, gnome_vfs_result_to_string (result));
+    }
+
+  /* /etc/passwd */
+  manager->users =
+    g_hash_table_new_full (g_str_hash, g_str_equal,
+			   g_free, (GDestroyNotify) g_object_run_dispose);
+  manager->users_by_uid = g_hash_table_new (g_direct_hash, g_direct_equal);
+  reload_passwd (manager);
+  error = NULL;
+  uri = g_filename_to_uri ("/etc/passwd", NULL, &error);
+  if (!uri)
+    {
+      g_critical ("Could not create URI for password file `%s': %s",
+		  GDMCONFIGFILE, error->message);
+      g_error_free (error);
+    }
+  else
+    {
+      result = gnome_vfs_monitor_add (&(manager->passwd_monitor), uri,
+				      GNOME_VFS_MONITOR_FILE,
+				      passwd_monitor_cb, manager);
+      g_free (uri);
+
+      if (result != GNOME_VFS_OK)
+	g_critical ("Could not install monitor for password file `%s': %s",
+		    GDMCONFIGFILE, gnome_vfs_result_to_string (result));
+    }
+
+
+  /* Displays */
+  manager->displays =
+    g_hash_table_new_full (g_str_hash, g_str_equal,
+			   g_free, (GDestroyNotify) g_object_run_dispose);
+  manager->displays_by_console = g_hash_table_new (g_direct_hash,
+						   g_direct_equal);
+  manager->results_q = g_async_queue_new ();
+  push_update_displays_func (manager);
+  manager->update_displays_id =
+    g_timeout_add_full (G_PRIORITY_LOW, 5000, push_update_displays_func,
+			manager, NULL);
+}
+
+
+/* ******************* *
+ *  GObject Functions  *
+ * ******************* */
+
+static void
+fusa_manager_finalize (GObject *object)
+{
+  FusaManager *manager;
+  OpResult *result;
+
+  manager = FUSA_MANAGER (object);
+
+  if (manager->update_displays_id)
+    g_source_remove (manager->update_displays_id);
+
+  if (manager->results_pull_id)
+    g_source_remove (manager->results_pull_id);
+
+  result = g_async_queue_try_pop (manager->results_q);
+  while (result)
+    {
+      op_result_free (result);
+      result = g_async_queue_try_pop (manager->results_q);
+    }
+  g_async_queue_unref (manager->results_q);
+
+  g_hash_table_destroy (manager->displays);
+  g_hash_table_destroy (manager->displays_by_console);
+
+  gnome_vfs_monitor_cancel (manager->shells_monitor);
+  g_hash_table_destroy (manager->shells);
+
+  gnome_vfs_monitor_cancel (manager->gdmconfig_monitor);
+  g_hash_table_destroy (manager->exclusions);
+  g_free (manager->global_face_dir);
+
+  gnome_vfs_monitor_cancel (manager->passwd_monitor);
+  g_hash_table_destroy (manager->users);
+  g_hash_table_destroy (manager->users_by_uid);
+
+  if (G_OBJECT_CLASS (fusa_manager_parent_class)->finalize)
+    (*G_OBJECT_CLASS (fusa_manager_parent_class)->finalize) (object);
+}
+
+
+/* *************************** *
+ *  GnomeVFSMonitor Callbacks  *
+ * *************************** */
+
+static void
+gdmconfig_monitor_cb (GnomeVFSMonitorHandle    *handle,
+		      const gchar              *text_uri,
+		      const gchar              *info_uri,
+		      GnomeVFSMonitorEventType  event_type,
+		      gpointer                  data)
+{
+  gboolean users_dirty, icons_dirty;
+
+  if (event_type != GNOME_VFS_MONITOR_EVENT_CHANGED)
+    return;
+
+  users_dirty = FALSE;
+  icons_dirty = FALSE;
+  reload_gdm_config (data, &users_dirty, &icons_dirty);
+
+  if (users_dirty)
+    reload_passwd (data);
+
+  if (icons_dirty)
+    g_hash_table_foreach (FUSA_MANAGER (data)->users,
+			  emit_user_icon_changed_signals, NULL);
+}
+
+static void
+shells_monitor_cb (GnomeVFSMonitorHandle    *handle,
+		   const gchar              *text_uri,
+		   const gchar              *info_uri,
+		   GnomeVFSMonitorEventType  event_type,
+		   gpointer                  data)
+{
+  if (event_type != GNOME_VFS_MONITOR_EVENT_CHANGED)
+    return;
+
+  reload_shells (data);
+  reload_passwd (data);
+}
+
+static void
+passwd_monitor_cb (GnomeVFSMonitorHandle    *handle,
+		   const gchar              *text_uri,
+		   const gchar              *info_uri,
+		   GnomeVFSMonitorEventType  event_type,
+		   gpointer                  user_data)
+{
+  if (event_type != GNOME_VFS_MONITOR_EVENT_CHANGED)
+    return;
+
+  reload_passwd (user_data);
+}
+
+
+/* ************************ *
+ *  User Updates Functions  *
+ * ************************ */
+
+static void
+emit_user_icon_changed_signals (gpointer key,
+			        gpointer value,
+			        gpointer user_data)
+{
+  _fusa_user_icon_changed (value);
+}
+
+static inline gboolean
+strv_equals_string_table (gchar      **strv,
+			  GHashTable  *table)
+{
+  guint i;
+
+  /* Ensure sizes are equal */
+  if (g_strv_length (strv) != g_hash_table_size (table))
+    return FALSE;
+
+  /* Ensure that everything in strv is also in table */
+  for (i = 0; strv[i] != NULL; i++)
+    {
+      if (!g_hash_table_lookup (table, strv[i]))
+	return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+check_user_file (const gchar *filename,
+		 uid_t        user,
+		 gssize       max_file_size,
+		 gboolean     relax_group,
+		 gboolean     relax_other)
+{
+  struct stat fileinfo;
+
+  if (max_file_size < 0)
+    max_file_size = G_MAXSIZE;
+
+  /* Exists/Readable? */
+  if (stat (filename, &fileinfo) < 0)
+    return FALSE;
+
+  /* Is a regular file */
+  if (G_UNLIKELY (!S_ISREG (fileinfo.st_mode)))
+    return FALSE;
+
+  /* Owned by user? */
+  if (G_UNLIKELY (fileinfo.st_uid != user))
+    return FALSE;
+
+  /* Group not writable or relax_group? */
+  if (G_UNLIKELY ((fileinfo.st_mode & S_IWGRP) == S_IWGRP && !relax_group))
+    return FALSE;
+
+  /* Other not writable or relax_other? */
+  if (G_UNLIKELY ((fileinfo.st_mode & S_IWOTH) == S_IWOTH && !relax_other))
+    return FALSE;
+
+  /* Size is kosher? */
+  if (G_UNLIKELY (fileinfo.st_size > max_file_size))
+    return FALSE;
+
+  return TRUE;
+}
+
+static void
+reload_gdm_config (FusaManager *manager,
+		   gboolean    *users_dirty,
+		   gboolean    *icons_dirty)
+{
+  GKeyFile *key_file;
+  GError *error;
+
+  error = NULL;
+  key_file = g_key_file_new ();
+
+  if (!g_key_file_load_from_file (key_file, GDMCONFIGFILE, G_KEY_FILE_NONE,
+				  &error))
+    {
+      g_critical ("Could not parse GDM configuration file `%s': %s",
+		  GDMCONFIGFILE, error->message);
+      g_error_free (error);
+    }
+  else
+    {
+      gchar *tmp;
+      gint tmp_max_width, tmp_max_height, tmp_max_icon_size;
+      gboolean tmp_allow_root;
+      gchar **excludev;
+      guint i;
+      static const gchar *exclude_default[] = DEFAULT_EXCLUDE;
+
+      tmp = g_key_file_get_value (key_file, "greeter", "MinimalUID", NULL);
+      if (tmp)
+	{
+	  uid_t tmp_uid;
+
+	  tmp_uid = strtoul (tmp, NULL, 10);
+	  g_free (tmp);
+
+	  if (tmp_uid == ULONG_MAX)
+	    tmp_uid = DEFAULT_MINIMAL_UID;
+
+	  if (tmp_uid != manager->minimal_uid)
+	    {
+	      manager->minimal_uid = tmp_uid;
+	      if (users_dirty)
+		*users_dirty = TRUE;
+	    }
+	}
+      else if (manager->minimal_uid != DEFAULT_MINIMAL_UID)
+	{
+	  manager->minimal_uid = DEFAULT_MINIMAL_UID;
+	  if (users_dirty)
+	    *users_dirty = TRUE;
+	}
+
+      excludev = g_key_file_get_string_list (key_file, "greeter", "Exclude",
+					     NULL, NULL);
+      if (!excludev)
+	excludev = g_strdupv ((gchar **) exclude_default);
+
+      if (!strv_equals_string_table (excludev, manager->exclusions))
+	{
+	  g_hash_table_foreach_remove (manager->exclusions,
+				       clear_hash_table_hrfunc, NULL);
+
+	  /* Always include the defaults */
+	  for (i = 0; exclude_default[i] != NULL; i++)
+	    g_hash_table_insert (manager->exclusions,
+				 g_strdup (exclude_default[i]),
+				 GUINT_TO_POINTER (TRUE));
+
+	  for (i = 0; excludev[i] != NULL; i++)
+	    g_hash_table_insert (manager->exclusions,
+				 excludev[i], GUINT_TO_POINTER (TRUE));
+
+	  if (users_dirty)
+	    *users_dirty = TRUE;
+
+	  g_free (excludev);
+	}
+      else
+	g_strfreev (excludev);
+
+      tmp = g_key_file_get_string (key_file, "greeter", "GlobalFaceDir", NULL);
+      if (!tmp)
+	{
+	  if (!manager->global_face_dir ||
+	      strcmp (manager->global_face_dir, DEFAULT_GLOBAL_FACE_DIR) != 0)
+	    {
+	      g_free (manager->global_face_dir);
+	      manager->global_face_dir = g_strdup (DEFAULT_GLOBAL_FACE_DIR);
+	    }
+	}
+      else
+	{
+	  if (!manager->global_face_dir ||
+	      strcmp (tmp, manager->global_face_dir) != 0)
+	    {
+	      g_free (manager->global_face_dir);
+	      manager->global_face_dir = g_strdup (tmp);
+	    }
+
+	  g_free (tmp);
+	}
+
+      tmp = g_key_file_get_value (key_file, "security", "UserMaxFile", NULL);
+      if (tmp)
+	{
+	  gsize tmp_user_max_file;
+
+	  tmp_user_max_file = strtol (tmp, NULL, 10);
+	  g_free (tmp);
+
+	  if (tmp_user_max_file == ULONG_MAX)
+	    tmp_user_max_file = DEFAULT_USER_MAX_FILE;
+
+	  if (tmp_user_max_file != manager->user_max_file)
+	    {
+	      manager->user_max_file = tmp_user_max_file;
+	      if (icons_dirty)
+		*icons_dirty = TRUE;
+	    }
+	}
+      else if (manager->user_max_file != DEFAULT_USER_MAX_FILE)
+	{
+	  manager->user_max_file = DEFAULT_USER_MAX_FILE;
+	  if (icons_dirty)
+	    *icons_dirty = TRUE;
+	}
+
+      tmp_allow_root = g_key_file_get_boolean (key_file, "security",
+					       "AllowRoot", NULL);
+      if ((tmp_allow_root && !manager->allow_root) ||
+	  (!tmp_allow_root && manager->allow_root))
+	{
+	  manager->allow_root = (tmp_allow_root != FALSE);
+	  if (users_dirty)
+	    *users_dirty = TRUE;
+	}
+      
+      tmp_max_width = g_key_file_get_integer (key_file, "gui", "MaxIconWidth",
+					      NULL);
+      if (tmp_max_width == -1)
+	tmp_max_width = DEFAULT_MAX_ICON_SIZE;
+
+      tmp_max_height = g_key_file_get_integer (key_file, "gui", "MaxIconHeight",
+					       NULL);
+      if (tmp_max_height == -1)
+	tmp_max_height = DEFAULT_MAX_ICON_SIZE;
+
+      tmp_max_icon_size = MAX (tmp_max_width, tmp_max_height);
+      if (tmp_max_icon_size != manager->max_icon_size)
+	{
+	  manager->max_icon_size = tmp_max_icon_size;
+	  if (icons_dirty)
+	    *icons_dirty = TRUE;
+	}
+    }
+
+  g_key_file_free (key_file);
+}
+
+static void
+reload_shells (FusaManager *manager)
+{
+  gchar *shell;
+
+  setusershell();
+
+  g_hash_table_foreach_remove (manager->shells, clear_hash_table_hrfunc, NULL);
+  for (shell = getusershell (); shell; shell = getusershell ())
+    {
+      g_hash_table_insert (manager->shells, g_strdup (shell),
+			   GUINT_TO_POINTER (TRUE));
+    }
+
+  endusershell ();
+}
+
+static void
+reload_passwd (FusaManager *manager)
+{
+  struct passwd *pwent;
+  GSList *old_users, *new_users, *list;
+  
+  old_users = NULL;
+  new_users = NULL;
+
+  g_hash_table_foreach (manager->users, listify_hash_values_hfunc, &old_users);
+  g_slist_foreach (old_users, (GFunc) g_object_ref, NULL);
+
+  /* Make sure we keep users who are logged in no matter what. */
+  for (list = old_users; list; list = list->next)
+    {
+      if (fusa_user_get_n_displays (list->data))
+	{
+	  g_object_freeze_notify (G_OBJECT (list->data));
+	  new_users = g_slist_prepend (new_users, g_object_ref (list->data));
+	}
+    }
+
+  setpwent ();
+
+  for (pwent = getpwent (); pwent; pwent = getpwent ())
+    {
+      FusaUser *user;
+
+      user = NULL;
+
+      /* Skip users below MinimalUID... */
+      if (pwent->pw_uid < manager->minimal_uid)
+	continue;
+
+      /* ...And users w/ invalid shells... */
+      if (!pwent->pw_shell ||
+	  !g_hash_table_lookup (manager->shells, pwent->pw_shell))
+	continue;
+
+      /* ...And explicitly excluded users */
+      if (g_hash_table_lookup (manager->exclusions, pwent->pw_name))
+	continue;
+
+      user = g_hash_table_lookup (manager->users, pwent->pw_name);
+
+      /* Update users already in the *new* list */
+      if (g_slist_find (new_users, user))
+	{
+	  _fusa_user_update (user, pwent);
+	  continue;
+	}
+
+      if (!user)
+	user = g_object_new (FUSA_TYPE_USER, "manager", manager, NULL);
+      else
+	g_object_ref (user);
+
+      /* Freeze & update users not already in the new list */
+      g_object_freeze_notify (G_OBJECT (user));
+      _fusa_user_update (user, pwent);
+
+      new_users = g_slist_prepend (new_users, user);
+    }
+  
+  endpwent ();
+
+  /* Go through and handle added users */
+  for (list = new_users; list; list = list->next)
+    {
+      if (!g_slist_find (old_users, list->data))
+	{
+	  g_hash_table_insert (manager->users,
+			       g_strdup (fusa_user_get_user_name (list->data)),
+			       g_object_ref (list->data));
+	  g_hash_table_insert (manager->users_by_uid,
+			       GINT_TO_POINTER (fusa_user_get_uid (list->data)),
+			       list->data);
+	  g_signal_emit (manager, signals[USER_ADDED], 0, list->data);
+	}
+    }
+
+  /* Go through and handle removed users */
+  for (list = old_users; list; list = list->next)
+    {
+      if (!g_slist_find (new_users, list->data))
+	{
+	  g_signal_emit (manager, signals[USER_REMOVED], 0, list->data);
+	  g_hash_table_remove (manager->users_by_uid,
+			       GINT_TO_POINTER (fusa_user_get_uid (list->data)));
+	  g_hash_table_remove (manager->users,
+			       fusa_user_get_user_name (list->data));
+	}
+    }
+
+  /* Cleanup */
+  g_slist_foreach (new_users, (GFunc) g_object_thaw_notify, NULL);
+  g_slist_foreach (new_users, (GFunc) g_object_unref, NULL);
+  g_slist_free (new_users);
+
+  g_slist_foreach (old_users, (GFunc) g_object_unref, NULL);
+  g_slist_free (old_users);
+}
+
+
+/* ********************************** *
+ *  Displays - Main Thread Functions  *
+ * ********************************** */
+
+static gboolean
+clean_displays_table (gpointer key,
+		      gpointer value,
+		      gpointer data)
+{
+  CleanDisplaysData *clean;
+  GSList *li;
+
+  clean = data;
+
+  li = g_slist_find (clean->displays, value);
+
+  if (!li)
+    {
+      g_signal_emit (clean->manager, signals[DISPLAY_REMOVED], 0, value);
+      g_hash_table_remove (clean->manager->displays_by_console,
+			   GINT_TO_POINTER (fusa_display_get_console (value)));
+      return TRUE;
+    }
+  else
+    {
+      g_object_unref (li->data);
+      clean->displays = g_slist_delete_link (clean->displays, li);
+    }
+
+  return FALSE;
+}
+
+static inline void
+process_display_updates (FusaManager *manager,
+			 OpResult    *result)
+{
+  CleanDisplaysData clean_data;
+  FusaDisplay *display;
+  FusaUser *user;
+  guint i;
+
+  clean_data.manager = manager;
+  clean_data.displays = NULL;
+
+  for (i = 0; i < result->updates.n_items; i++)
+    {
+      if (result->updates.items[i].username != NULL)
+	{
+	  user = fusa_manager_get_user (manager,
+					result->updates.items[i].username);
+	}
+      else
+	user = NULL;
+
+      display = real_get_display (manager, result->updates.items[i].name);
+
+      if (display)
+	{
+	  _fusa_display_set_user (display, user);
+	  g_object_ref (display);
+	}
+      else
+	{
+	  gint console;
+	  gboolean nested;
+
+	  console = -1;
+	  nested = (result->updates.items[i].location[0] == ':');
+
+	  /* Xnest: We don't need to loop to get around Xnest-in-Xnest insanity
+	   * becase all existing FusaDisplays will have a console set from their
+	   * insert -- plus that's just crackish anyways. */
+	  if (nested)
+	    {
+	      FusaDisplay *display;
+	      gchar *ptr;
+
+	      /* Strip the ".0" */
+	      ptr = strchr (result->updates.items[i].location, '.');
+	      if (ptr)
+		*ptr = '\0';
+
+	      display = real_get_display (manager,
+					  result->updates.items[i].location);
+
+	      if (display)
+		console = fusa_display_get_console (display);
+	    }
+	  /* Console */
+	  else if (sscanf (result->updates.items[i].location, "%d",
+			   &console) != 1)
+	    {
+	      g_critical ("Invalid display location returned from GDM: `%s'.",
+			  result->updates.items[i].location);
+	    }
+
+	  display = _fusa_display_new (manager,
+				       result->updates.items[i].name, user,
+				       console, nested);
+
+	  g_hash_table_insert (manager->displays,
+			       g_strdup (result->updates.items[i].name),
+			       g_object_ref (display));
+	  g_hash_table_insert (manager->displays_by_console,
+			       GINT_TO_POINTER (console), display);
+
+	  g_signal_emit (manager, signals[DISPLAY_ADDED], 0, display);
+	}
+
+      clean_data.displays = g_slist_prepend (clean_data.displays, display);
+    }
+
+  g_hash_table_foreach_remove (manager->displays, clean_displays_table,
+			       &clean_data);
+}
+
+static void
+display_closure_free (DisplayClosure *closure)
+{
+  if (closure->data && closure->notify)
+    (*closure->notify) (closure->data);
+
+  g_free (closure);
+}
+
+static void
+op_result_free (OpResult *result)
+{
+  if (!result)
+    return;
+
+  switch (result->type)
+    {
+    case OP_RESULT_DISPLAY_UPDATES:
+      {
+	guint i;
+
+	for (i = 0; i < result->updates.n_items; i++)
+	  {
+	    g_free (result->updates.items[i].name);
+	    g_free (result->updates.items[i].username);
+	    g_free (result->updates.items[i].location);
+	  }
+
+	g_free (result->updates.items);
+      }
+      break;
+    case OP_RESULT_NEW_DISPLAY:
+      display_closure_free (result->new.closure);
+      g_free (result->new.name);
+      break;
+    case OP_RESULT_DISPLAY_ACTIVATED:
+      g_object_unref (result->activate.display);
+      display_closure_free (result->activate.closure);
+      break;
+
+    case OP_RESULT_ERROR:
+      g_error_free (result->error.error);
+      display_closure_free (result->error.closure);
+      break;
+    }
+
+  g_free (result);
+}
+
+static gboolean
+results_pull_func (gpointer data)
+{
+  FusaManager *manager;
+  OpResult *result;
+
+  GDK_THREADS_ENTER ();
+
+  manager = data;
+  g_async_queue_lock (manager->results_q);
+
+  result = g_async_queue_try_pop_unlocked (manager->results_q);
+  g_assert (result);
+
+  while (result)
+    {
+      switch (result->type)
+	{
+	case OP_RESULT_DISPLAY_UPDATES:
+	  process_display_updates (manager, result);
+	  break;
+
+	case OP_RESULT_DISPLAY_ACTIVATED:
+	  if (result->activate.closure->func)
+	    (*result->activate.closure->func) (manager,
+					       result->activate.display, NULL,
+					       result->activate.closure->data);
+	  break;
+
+	case OP_RESULT_NEW_DISPLAY:
+	  if (result->activate.closure->func)
+	    {
+	      FusaDisplay *display;
+
+	      display = real_get_display (manager, result->new.name);
+	      (*result->activate.closure->func) (manager, display, NULL,
+						 result->new.closure->data);
+	    }
+	  break;
+
+	case OP_RESULT_ERROR:
+	  if (result->error.closure->func)
+	    (*result->error.closure->func) (manager, NULL, NULL,
+					    result->error.closure->data);
+	  break;
+	}
+
+      op_result_free (result);
+
+      result = g_async_queue_try_pop_unlocked (manager->results_q);
+    }
+
+  manager->results_pull_id = 0;
+
+  g_async_queue_unlock (manager->results_q);
+
+  GDK_THREADS_LEAVE ();
+
+  return FALSE;
+}
+
+static void
+push_dm_op (FusaManager                *manager,
+	    OpType                      type,
+	    GdkScreen                  *screen,
+	    FusaDisplay                *display,
+	    FusaManagerDisplayCallback  func,
+	    gpointer                    data,
+	    GDestroyNotify              notify)
+{
+  Op *op;
+
+  op = g_new (Op, 1);
+
+  op->type = type;
+  op->manager = g_object_ref (manager);
+  op->screen = (screen ? g_object_ref (screen) : NULL);
+  if (display)
+    {
+      op->display = g_object_ref (display);
+      op->console = fusa_display_get_console (display);
+    }
+  else
+    {
+      op->display = NULL;
+      op->console = -1;
+    }
+  op->closure = g_new (DisplayClosure, 1);
+  op->closure->func = func;
+  op->closure->data = data;
+  op->closure->notify = notify;
+
+  g_async_queue_push (op_q, op);
+}
+
+static gboolean
+push_update_displays_func (gpointer data)
+{
+  push_dm_op (data, OP_UPDATE_DISPLAYS, NULL, NULL, NULL, NULL, NULL);
+
+  return TRUE;
+}
+
+
+/* ******************************** *
+ *  Displays - IO Thread Functions  *
+ * ******************************** */
+
+static void
+push_new_result (FusaManager *manager,
+		 Op          *op,
+		 const gchar *name)
+{
+  OpResult *op_result;
+    
+  op_result = g_new0 (OpResult, 1);
+  op_result->new.type = OP_RESULT_NEW_DISPLAY;
+  op_result->new.closure = op->closure;
+  op->closure = NULL;
+  op_result->new.name = g_strdup (name);
+
+  GDK_THREADS_ENTER ();
+  g_async_queue_lock (manager->results_q);
+  g_async_queue_push_unlocked (manager->results_q, op_result);
+  if (!manager->results_pull_id)
+    manager->results_pull_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+						results_pull_func,
+						manager, NULL);
+
+  g_async_queue_unlock (manager->results_q);
+  GDK_THREADS_LEAVE ();
+}
+
+static void
+push_error_result (FusaManager *manager,
+		   Op          *op,
+		   const gchar *result)
+{
+  OpResult *op_result;
+  gint code;
+  const gchar *message;
+    
+  op_result = g_new0 (OpResult, 1);
+  op_result->type = OP_RESULT_ERROR;
+
+  if (result == NULL || sscanf (result, "ERROR %d", &code) != 1)
+    {
+      code = FUSA_MANAGER_DM_ERROR_UNKNOWN;
+      message = _("The display manager could not be contacted for unknown reasons.");
+    }
+  else
+    {
+      switch (code)
+	{
+	case FUSA_MANAGER_DM_ERROR_NOT_RUNNING:
+	  message = _("The display manager is not running or too old.");
+	  break;
+	case FUSA_MANAGER_DM_ERROR_FLEXI_LIMIT_REACHED:
+	  message = _("The configured limit of flexible servers has been reached.");
+	  break;
+	case FUSA_MANAGER_DM_ERROR_X_ERROR:
+	  message = _("There was an unknown error starting X.");
+	  break;
+	case FUSA_MANAGER_DM_ERROR_X_FAILED:
+	  message = _("The X server failed to finish starting.");
+	  break;
+	case FUSA_MANAGER_DM_ERROR_X_LIMIT_REACHED:
+	  message = _("There are too many X sessions running.");
+	  break;
+	case FUSA_MANAGER_DM_ERROR_XNEST_AUTH_ERROR:
+	  message = _("The nested X server (Xnest) cannot connect to your current X server.");
+	  break;
+	case FUSA_MANAGER_DM_ERROR_X_NOT_FOUND:
+	  message = _("The X server in the GDM configuration could not be found.");
+	  break;
+	case FUSA_MANAGER_DM_ERROR_LOGOUT_ACTION:
+	  message = _("Trying to set an unknown logout action, or trying to set a logout action which is not available.");
+	  break;
+	case FUSA_MANAGER_DM_ERROR_CONSOLES_UNSUPPORTED:
+	  message = _("Virtual terminals not supported.");
+	  break;
+	case FUSA_MANAGER_DM_ERROR_INVALID_CONSOLE:
+	  message = _("Invalid virtual terminal number.");
+	  break;
+	case FUSA_MANAGER_DM_ERROR_UNKNOWN_CONFIG:
+	  message = _("Trying to update an unsupported configuration key.");
+	  break;
+	case FUSA_MANAGER_DM_ERROR_PERMISSIONS:
+	  message = _("~/.Xauthority file badly configured or missing.");
+	  break;
+	case FUSA_MANAGER_DM_ERROR_TOO_MANY_MESSAGES:
+	  message = _("Too many messages were sent to the display manager, and it hung up.");
+	  break;
+	default:
+	  code = FUSA_MANAGER_DM_ERROR_OTHER;
+	  message = _("The display manager sent an unknown error message.");
+	  break;
+	}
+    }
+
+  op_result->error.error = g_error_new (FUSA_MANAGER_DM_ERROR, code,
+					"%s\n%s", message, result);
+  op_result->error.closure = op->closure;
+  op->closure = NULL;
+
+  GDK_THREADS_ENTER ();
+  g_async_queue_lock (manager->results_q);
+  g_async_queue_push_unlocked (manager->results_q, op_result);
+  if (!manager->results_pull_id)
+    manager->results_pull_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+						results_pull_func,
+						manager, NULL);
+
+  g_async_queue_unlock (manager->results_q);
+  GDK_THREADS_LEAVE ();
+}
+
+static gboolean
+dm_op_update_displays (FusaManager *manager)
+{
+  gchar *result;
+  gboolean retval;
+
+  result = gdmcomm_call_gdm (GDM_SUP_CONSOLE_SERVERS, NULL,
+			     MINIMAL_GDM_VERSION, 5);
+  retval = (result && strncmp (result, "OK ", 3) == 0);
+
+  /* Success */
+  if (retval)
+    {
+      gchar **displays;
+
+      displays = g_strsplit (result + 3, ";", -1);
+      if (displays)
+	{
+	  guint i;
+		
+	  i = g_strv_length (displays);
+	  if (i)
+	    {
+	      OpResult *op_result;
+
+	      op_result = g_new0 (OpResult, 1);
+
+	      op_result->type = OP_RESULT_DISPLAY_UPDATES;
+	      op_result->updates.items = g_new0 (DisplayUpdate, i);
+	      op_result->updates.n_items = i;
+
+	      while (i > 0)
+		{
+		  gchar **args;
+
+		  i--;
+
+		  args = g_strsplit (displays[i], ",", 3);
+
+		  /* Display name (e.g. :0) */
+		  op_result->updates.items[i].name = args[0];
+
+		  /* Username */
+		  if (args[1] != NULL && args[1][0] != '\0')
+		    op_result->updates.items[i].username = args[1];
+		  else
+		    {
+		      op_result->updates.items[i].username = NULL;
+		      g_free (args[1]);
+		    }
+
+		  /* VT or display name Location (e.g "7" or ":0.0") */
+		  op_result->updates.items[i].location = args[2];
+
+		  g_free (args);
+		}
+
+	      GDK_THREADS_ENTER ();
+	      g_async_queue_lock (manager->results_q);
+	      g_async_queue_push_unlocked (manager->results_q, op_result);
+	      if (!manager->results_pull_id)
+		manager->results_pull_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+							    results_pull_func,
+							    manager, NULL);
+	      g_async_queue_unlock (manager->results_q);
+	      GDK_THREADS_LEAVE ();
+	    }
+
+	  g_strfreev (displays);
+	}
+    }
+
+  g_free (result);
+
+  return retval;
+}
+
+static inline void
+dm_op_activate_display (FusaManager *manager,
+		        Op          *op)
+{
+  gchar *auth_cookie, *command, *result;
+
+  GDK_THREADS_ENTER ();
+  auth_cookie = g_strdup (gdmcomm_get_auth_cookie (op->screen));
+  GDK_THREADS_LEAVE ();
+
+  command = g_strdup_printf (GDM_SUP_SET_VT " %d", op->console);
+  result = gdmcomm_call_gdm (command, auth_cookie, MINIMAL_GDM_VERSION, 5);
+  g_free (command);
+  g_free (auth_cookie);
+
+  /* Success, refresh the displays cache */
+  if (result && strncmp (result, "OK", 2) == 0)
+    {
+      OpResult *op_result;
+    
+      op_result = g_new0 (OpResult, 1);
+      op_result->activate.type = OP_RESULT_DISPLAY_ACTIVATED;
+      op_result->activate.display = g_object_ref (op->display);
+      op_result->activate.closure = op->closure;
+      op->closure = NULL;
+
+      GDK_THREADS_ENTER ();
+      g_async_queue_lock (manager->results_q);
+      g_async_queue_push_unlocked (manager->results_q, op_result);
+      if (!manager->results_pull_id)
+	manager->results_pull_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+						    results_pull_func,
+						    manager, NULL);
+
+      g_async_queue_unlock (manager->results_q);
+      GDK_THREADS_LEAVE ();
+
+      dm_op_update_displays (manager);
+    }
+  /* Error */
+  else
+    push_error_result (manager, op, result);
+  
+  g_free (result);
+}
+
+static inline void
+dm_op_new_console (FusaManager *manager,
+		   Op          *op)
+{
+  gchar *auth_cookie, *result;
+
+  GDK_THREADS_ENTER ();
+  auth_cookie = g_strdup (gdmcomm_get_auth_cookie (op->screen));
+  GDK_THREADS_LEAVE ();
+
+  result = gdmcomm_call_gdm (GDM_SUP_FLEXI_XSERVER, auth_cookie,
+			     MINIMAL_GDM_VERSION, 5);
+  g_free (auth_cookie);
+
+  /* Success, refresh the displays cache */
+  if (result && strncmp (result, "OK", 2) == 0)
+    {
+      dm_op_update_displays (manager);
+      push_new_result (manager, op, result + 3);
+    }
+  /* Error */
+  else
+    push_error_result (manager, op, result);
+
+  g_free (result);
+}
+
+static inline void
+dm_op_new_xnest (FusaManager *manager,
+		 Op          *op)
+{
+  gchar *auth_cookie, *mit_cookie, *command, *result;
+
+  GDK_THREADS_ENTER ();
+  auth_cookie = g_strdup (gdmcomm_get_auth_cookie (op->screen));
+  mit_cookie = gdmcomm_get_a_cookie (op->screen, FALSE);
+  command = g_strdup_printf (GDM_SUP_FLEXI_XNEST " %s %d %s %s",
+			     gdk_display_get_name (gdk_screen_get_display (op->screen)),
+			     (int) getuid (),
+			     mit_cookie,
+			     XauFileName ());
+  GDK_THREADS_LEAVE ();
+  g_free (mit_cookie);
+
+  result = gdmcomm_call_gdm (command, auth_cookie, MINIMAL_GDM_VERSION, 5);
+  g_free (command);
+  g_free (auth_cookie);
+
+  if (result && strncmp (result, "OK ", 3) == 0)
+    {
+      dm_op_update_displays (manager);
+      push_new_result (manager, op, result + 3);
+    }
+  /* Error */
+  else
+    push_error_result (manager, op, result);
+
+  g_free (result);
+}
+
+static gpointer
+dm_op_threadfunc (gpointer data)
+{
+  Op *op;
+
+  op = g_async_queue_pop (op_q);
+  while (op)
+    {
+      switch (op->type)
+	{
+	case OP_UPDATE_DISPLAYS:
+	  dm_op_update_displays (op->manager);
+	  break;
+
+	case OP_ACTIVATE_DISPLAY:
+	  dm_op_activate_display (op->manager, op);
+	  break;
+
+	case OP_NEW_CONSOLE:
+	  dm_op_new_console (op->manager, op);
+	  break;
+
+	case OP_NEW_XNEST:
+	  dm_op_new_xnest (op->manager, op);
+	  break;
+	}
+
+      GDK_THREADS_ENTER ();
+      if (op->screen)
+	g_object_unref (op->screen);
+
+      if (op->display)
+	g_object_unref (op->display);
+
+      if (op->manager)
+	g_object_unref (op->manager);
+      GDK_THREADS_LEAVE ();
+
+      g_free (op);
+
+      op = g_async_queue_pop (op_q);
+    }
+  
+  return NULL;
+}
+
+
+/* ******************* *
+ *  Utility Functions  *
+ * ******************* */
+
+static void
+listify_hash_values_hfunc (gpointer key,
+			   gpointer value,
+			   gpointer user_data)
+{
+  GSList **list = user_data;
+
+  *list = g_slist_prepend (*list, value);
+}
+
+static gboolean
+clear_hash_table_hrfunc (gpointer key,
+			 gpointer value,
+			 gpointer user_data)
+{
+  return TRUE;
+}
+
+static FusaDisplay *
+real_get_display (FusaManager *manager,
+		  const gchar *name)
+{
+  FusaDisplay *display;
+  gchar *ptr, *key;
+
+  ptr = strchr (name, '.');
+  if (ptr)
+    key = g_strndup (name, ptr - name);
+  else
+    key = g_strdup (name);
+
+  display = g_hash_table_lookup (manager->displays, key);
+  g_free (key);
+
+  return display;
+}
+
+
+/* ************************************************************************** *
+ *  Semi-Private API                                                          *
+ * ************************************************************************** */
+
+static GdkPixbuf *
+render_icon_from_home (FusaManager  *manager,
+		       FusaUser     *user,
+		       GtkWidget    *widget,
+		       gint          icon_size)
+{
+  GdkPixbuf *retval;
+  const gchar *homedir, *username;
+  uid_t uid;
+  gchar *path;
+  GnomeVFSURI *uri;
+  gboolean is_local;
+
+  homedir = fusa_user_get_home_directory (user);
+  uid = fusa_user_get_uid (user);
+  username = NULL;
+
+  /* special case: look at parent of home to detect autofs
+     this is so we don't try to trigger an automount */
+  path = g_path_get_dirname (homedir);
+  uri = gnome_vfs_uri_new (path);
+  is_local = gnome_vfs_uri_is_local (uri);
+  gnome_vfs_uri_unref (uri);
+  g_free (path);
+
+  /* now check that home dir itself is local */
+  if (is_local)
+    {
+      uri = gnome_vfs_uri_new (homedir);
+      is_local = gnome_vfs_uri_is_local (uri);
+      gnome_vfs_uri_unref (uri);
+    }
+
+  /* only look at local home directories so we don't try to
+     read from remote (e.g. NFS) volumes */
+  if (!is_local)
+    return NULL;
+
+  /* First, try "~/.face" */
+  path = g_build_filename (homedir, ".face", NULL);
+  if (check_user_file (path, uid, manager->user_max_file,
+		       manager->relax_group, manager->relax_other))
+    retval = gdk_pixbuf_new_from_file_at_size (path, icon_size, icon_size, NULL);
+  else
+    retval = NULL;
+  g_free (path);
+
+  /* Next, try "~/.face.icon" */
+  if (!retval)
+    {
+      path = g_build_filename (homedir, ".face.icon", NULL);
+      if (check_user_file (path, uid, manager->user_max_file,
+			   manager->relax_group, manager->relax_other))
+	retval = gdk_pixbuf_new_from_file_at_size (path, icon_size, icon_size,
+						   NULL);
+      else
+	retval = NULL;
+
+      g_free (path);
+    }
+
+  /* Still nothing, try the user's personal GDM config */
+  if (!retval)
+    {
+      path = g_build_filename (homedir, ".gnome", "gdm", NULL);
+      if (check_user_file (path, uid, manager->user_max_file,
+			   manager->relax_group, manager->relax_other))
+	{
+	  GKeyFile *keyfile;
+	  gchar *icon_path;
+
+	  keyfile = g_key_file_new ();
+	  g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL);
+
+	  icon_path = g_key_file_get_string (keyfile, "face", "picture", NULL);
+	  if (icon_path &&
+	      check_user_file (icon_path, uid, manager->user_max_file,
+			       manager->relax_group, manager->relax_other))
+	    retval = gdk_pixbuf_new_from_file_at_size (path, icon_size,
+						       icon_size, NULL);
+	  else
+	    retval = NULL;
+
+	  g_free (icon_path);
+	  g_key_file_free (keyfile);
+	}
+      else
+	retval = NULL;
+
+      g_free (path);
+    }
+
+  /* Still nothing, try "~/.gnome/photo" */
+  if (!retval)
+    {
+      path = g_build_filename (homedir, ".gnome", "photo", NULL);
+      if (check_user_file (path, uid, manager->user_max_file,
+			   manager->relax_group, manager->relax_other))
+	retval = gdk_pixbuf_new_from_file_at_size (path, icon_size, icon_size,
+						   NULL);
+      else
+	retval = NULL;
+
+      g_free (path);
+    }
+
+  return retval;
+}
+
+GdkPixbuf *
+_fusa_manager_render_icon (FusaManager  *manager,
+			   FusaUser     *user,
+			   GtkWidget    *widget,
+			   gint          icon_size)
+{
+  GdkPixbuf *retval;
+  const gchar *username;
+  uid_t uid;
+  gchar *path;
+
+  g_return_val_if_fail (FUSA_IS_MANAGER (manager), NULL);
+  g_return_val_if_fail (FUSA_IS_USER (user), NULL);
+  g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), NULL);
+  g_return_val_if_fail (icon_size > 12, NULL);
+
+  retval = render_icon_from_home (manager, user, widget, icon_size);
+
+  if (retval)
+    return retval;
+
+  uid = fusa_user_get_uid (user);
+
+  /* Try ${GlobalFaceDir}/${username} */
+  if (!retval)
+    {
+      username = fusa_user_get_user_name (user);
+      path = g_build_filename (manager->global_face_dir, username, NULL);
+      if (check_user_file (path, uid, manager->user_max_file,
+			   manager->relax_group, manager->relax_other))
+	retval = gdk_pixbuf_new_from_file_at_size (path, icon_size, icon_size,
+						   NULL);
+      else
+	retval = NULL;
+
+      g_free (path);
+    }
+
+  /* Finally, ${GlobalFaceDir}/${username}.png */
+  if (!retval)
+    {
+      path = g_build_filename (manager->global_face_dir, username, ".png", NULL);
+      if (check_user_file (path, uid, manager->user_max_file,
+			   manager->relax_group, manager->relax_other))
+	retval = gdk_pixbuf_new_from_file_at_size (path, icon_size, icon_size,
+						   NULL);
+      else
+	retval = NULL;
+
+      g_free (path);
+    }
+
+  /* Nothing yet, use stock icon */
+  if (!retval)
+    {
+      GtkIconTheme *theme;
+      GdkPixbuf *icon;
+
+      if (widget && gtk_widget_has_screen (widget))
+	theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
+      else
+	theme = gtk_icon_theme_get_default ();
+
+      icon = gtk_icon_theme_load_icon (theme, DEFAULT_USER_ICON, icon_size,
+					 0, NULL);
+
+      if (!icon)
+	icon = gtk_icon_theme_load_icon (theme, GTK_STOCK_MISSING_IMAGE,
+					   icon_size, 0, NULL);
+
+      retval = gdk_pixbuf_copy (icon);
+      g_object_unref (icon);
+    }
+
+  return retval;
+}
+
+/* ************************************************************************** *
+ *  Public API                                                                *
+ * ************************************************************************** */
+
+/**
+ * fusa_manager_ref_default:
+ * 
+ * Retrieves a reference to the default manager object. This reference must be
+ * released with g_object_unref() when no longer needed.
+ * 
+ * Returns: a reference to a #FusaManager object.
+ **/
+FusaManager *
+fusa_manager_ref_default (void)
+{
+  if (default_manager)
+    g_object_ref (default_manager);
+  else
+    {
+      default_manager = g_object_new (FUSA_TYPE_MANAGER, NULL);
+      g_object_add_weak_pointer (G_OBJECT (default_manager),
+				 (gpointer *) &default_manager);
+    }
+
+  return default_manager;
+}
+
+/**
+ * fusa_manager_get_display:
+ * @manager: the manager to query.
+ * @name: the name of the X11 display to retrieve (e.g. ":0").
+ * 
+ * Retrieves a pointer to the #FusaDisplay object from @manager which
+ * corresponds to the @name display. The returned value is not a reference, and
+ * should not be released.
+ * 
+ * Returns: a pointer to a #FusaDisplay object.
+ **/
+FusaDisplay *
+fusa_manager_get_display (FusaManager *manager,
+			  const gchar *name)
+{
+  g_return_val_if_fail (FUSA_IS_MANAGER (manager), NULL);
+  g_return_val_if_fail (name != NULL && name[0] == ':', NULL);
+
+  return real_get_display (manager, name);
+}
+
+/**
+ * fusa_manager_get_display_by_screen:
+ * @manager: the manager to query.
+ * @screen: the GDK representation of a screen.
+ * 
+ * Convenience function which retrieves a pointer to the #FusaDisplay object
+ * from @manager which corresponds to the @screen display. The returned value is
+ * not a reference, and should not be released.
+ * 
+ * Returns: a pointer to a #FusaDisplay object.
+ **/
+FusaDisplay *
+fusa_manager_get_display_by_screen (FusaManager *manager,
+				    GdkScreen   *screen)
+{
+  g_return_val_if_fail (FUSA_IS_MANAGER (manager), NULL);
+  g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
+
+  return real_get_display (manager,
+			   gdk_display_get_name (gdk_screen_get_display (screen)));
+}
+
+/**
+ * fusa_manager_list_displays:
+ * @manager: the manager to query.
+ * 
+ * Retrieves a newly-allocated list of pointers to #FusaDisplay objects known to
+ * @manager. This list must be freed with g_slist_free() when no longer needed,
+ * but the list items themselves are not references and should not be released.
+ * 
+ * Returns: a singly-linked list of pointers to #FusaDisplay objects.
+ **/
+GSList *
+fusa_manager_list_displays (FusaManager *manager)
+{
+  GSList *retval;
+
+  g_return_val_if_fail (FUSA_IS_MANAGER (manager), NULL);
+
+  retval = NULL;
+  g_hash_table_foreach (manager->displays, listify_hash_values_hfunc, &retval);
+
+  return retval;
+}
+
+/**
+ * fusa_manager_activate_display:
+ * @manager: the manager to query.
+ * @display: the display to switch to.
+ * @screen: the screen which the application is running on.
+ * 
+ * Attempts to asynchronously change the current display to @display from
+ * @screen using @manager. If @display is a console-backed display, the display
+ * will switch. If @display is a nested (Xnest) display, the window will be
+ * activated. If this action was successful, the "display-activated" signal will
+ * be emitted.
+ **/
+void
+fusa_manager_activate_display (FusaManager                *manager,
+			       FusaDisplay                *display,
+			       GdkScreen                  *screen,
+			       FusaManagerDisplayCallback  func,
+			       gpointer                    data,
+			       GDestroyNotify              notify)
+{
+  FusaDisplay *this_display;
+  gint console;
+
+  this_display =
+    real_get_display (manager,
+		      gdk_display_get_name (gdk_screen_get_display (screen)));
+
+  if (display == this_display)
+    return;
+
+  console = fusa_display_get_console (display);
+  if (console != fusa_display_get_console (this_display))
+    push_dm_op (manager, OP_ACTIVATE_DISPLAY, screen, display, func, data, notify);
+  /* Xnest on this console, find & activate it. */
+  else 
+    {
+      Display *xdisplay;
+      Screen *xscreen;
+      Window xrootwin, dummy, client;
+      Window *children;
+      unsigned int n_children;
+
+      /* Modified from xc/programs/xlsclients/xlsclients.c
+       * Copyright 1989, 1998  The Open Group */
+      xscreen = gdk_x11_screen_get_xscreen (screen);
+      xdisplay = DisplayOfScreen (xscreen);
+      xrootwin = RootWindowOfScreen (xscreen);
+
+      if (XQueryTree (xdisplay, xrootwin, &dummy, &dummy,
+		      &children, &n_children))
+	{
+	  guint i;
+	  const gchar *display_name;
+	  gboolean found;
+
+	  display_name = fusa_display_get_name (display);
+	  found = FALSE;
+
+	  for (i = 0; !found && i < n_children; i++)
+	    {
+	      XTextProperty tp;
+	      char *wmname;
+	      char **client_argv;
+	      int client_argc;
+
+	      client_argv = NULL;
+	      client_argc = 0;
+	      wmname = NULL;
+	      tp.value = NULL;
+	      tp.encoding = None;
+	      client = XmuClientWindow (xdisplay, children[i]);
+
+	      /* We intentionally don't strip any trailing dots here because
+	       * GDM doesn't use them in it's exec'ing of Xnest -- anyways, if
+	       * client is Xnest and argv[1] == display_name, activate it. */
+	      if (!client || !XGetWMName (xdisplay, client, &tp) || !tp.value)
+		continue;
+	      
+	      if (strcmp ("Xnest", (const char *) tp.value) == 0 &&
+		  XGetCommand (xdisplay, client, &client_argv, &client_argc) &&
+		  client_argc > 1 &&
+		  strcmp (display_name, client_argv[1]) == 0)
+		{
+		  XEvent xev;
+
+		  /* Modified from libwnck/libwnck/xutils.c
+		   * Copyright 2001-2003 Havoc Pennington.
+		   * Copyright 2002 Anders Carlson
+		   * Copyright 2004-2005 Elijah Newren */
+		  xev.xclient.type = ClientMessage;
+		  xev.xclient.serial = 0;
+		  xev.xclient.send_event = True;
+		  xev.xclient.display = xdisplay;
+		  xev.xclient.window = client;
+		  xev.xclient.message_type = XInternAtom (xdisplay,
+							  "_NET_ACTIVE_WINDOW",
+							  FALSE);
+		  xev.xclient.format = 32;
+		  xev.xclient.data.l[0] = 2;
+		  xev.xclient.data.l[1] = gtk_get_current_event_time ();
+		  xev.xclient.data.l[2] = 0;
+		  xev.xclient.data.l[3] = 0;
+		  xev.xclient.data.l[4] = 0;
+
+		  XSendEvent (xdisplay, xrootwin, False,
+			      SubstructureRedirectMask | SubstructureNotifyMask,
+			      &xev);
+		  found = TRUE;
+		}
+
+	      if (client_argv)
+		XFreeStringList (client_argv);
+
+	      if (tp.value)
+		XFree (tp.value);
+	    }
+
+	  XFree (children);
+
+	  if (func)
+	    (*func) (manager, display, NULL, data);
+
+	  if (data && notify)
+	    (*notify) (data);
+	}
+    }
+}
+
+/**
+ * fusa_manager_new_console:
+ * @manager: the manager to query.
+ * @screen: the screen which the application is running on.
+ * 
+ * Attempts to asynchronously create a new console-backed display. If the action
+ * was successful, the "display-opened" signal will be emitted.
+ **/
+void
+fusa_manager_new_console (FusaManager                *manager,
+			  GdkScreen                  *screen,
+			  FusaManagerDisplayCallback  func,
+			  gpointer                    data,
+			  GDestroyNotify              notify)
+{
+  g_return_if_fail (FUSA_IS_MANAGER (manager));
+
+  push_dm_op (manager, OP_NEW_CONSOLE, screen, NULL, func, data, notify);
+}
+
+/**
+ * fusa_manager_new_xnest:
+ * @manager: the manager to query.
+ * @screen: the screen which the application is running on.
+ * 
+ * Attempts to asynchronously create a new nested (Xnest) display. If the action
+ * was successful, the "display-opened" signal will be emitted.
+ **/
+void
+fusa_manager_new_xnest (FusaManager                *manager,
+			GdkScreen                  *screen,
+			FusaManagerDisplayCallback  func,
+			gpointer                    data,
+			GDestroyNotify              notify)
+{
+  g_return_if_fail (FUSA_IS_MANAGER (manager));
+
+  push_dm_op (manager, OP_NEW_XNEST, screen, NULL, func, data, notify);
+}
+
+/**
+ * fusa_manager_get_user:
+ * @manager: the manager to query.
+ * @username: the login name of the user to get.
+ * 
+ * Retrieves a pointer to the #FusaUser object for the login named @username
+ * from @manager. This pointer is not a reference, and should not be released.
+ * 
+ * Returns: a pointer to a #FusaUser object.
+ **/
+FusaUser *
+fusa_manager_get_user (FusaManager *manager,
+		       const gchar *username)
+{
+  FusaUser *user;
+
+  g_return_val_if_fail (FUSA_IS_MANAGER (manager), NULL);
+  g_return_val_if_fail (username != NULL && username[0] != '\0', NULL);
+
+  user = g_hash_table_lookup (manager->users, username);
+
+  if (!user)
+    {
+      struct passwd *pwent;
+
+      pwent = getpwnam (username);
+
+      if (pwent)
+	{
+	  user = g_object_new (FUSA_TYPE_USER, "manager", manager, NULL);
+	  _fusa_user_update (user, pwent);
+	  g_hash_table_insert (manager->users, g_strdup (pwent->pw_name), user);
+	  g_hash_table_insert (manager->users_by_uid,
+			       GINT_TO_POINTER (pwent->pw_uid), user);
+	  g_signal_emit (manager, signals[USER_ADDED], 0, user);
+	}
+    }
+
+  return user;
+}
+
+/**
+ * fusa_manager_get_user_by_uid:
+ * @manager: the manager to query.
+ * @uid: the ID of the user to get.
+ * 
+ * Retrieves a pointer to the #FusaUser object for the @uid login from @manager.
+ * This pointer is not a reference, and should not be released.
+ * 
+ * Returns: a pointer to a #FusaUser object.
+ **/
+FusaUser *
+fusa_manager_get_user_by_uid (FusaManager *manager,
+			      uid_t        uid)
+{
+  FusaUser *user;
+
+  g_return_val_if_fail (FUSA_IS_MANAGER (manager), NULL);
+  g_return_val_if_fail (uid >= 0, NULL);
+
+  user = g_hash_table_lookup (manager->users_by_uid, GINT_TO_POINTER (uid));
+
+  if (!user)
+    {
+      struct passwd *pwent;
+
+      pwent = getpwuid (uid);
+
+      if (pwent)
+	{
+	  user = g_object_new (FUSA_TYPE_USER, "manager", manager, NULL);
+	  _fusa_user_update (user, pwent);
+	  g_hash_table_insert (manager->users, g_strdup (pwent->pw_name), user);
+	  g_hash_table_insert (manager->users_by_uid,
+			       GINT_TO_POINTER (pwent->pw_uid), user);
+	  g_signal_emit (manager, signals[USER_ADDED], 0, user);
+	}
+    }
+
+  return user;
+}
+
+/**
+ * fusa_manager_list_displays:
+ * @manager: the manager to query.
+ * 
+ * Retrieves a newly-allocated list of pointers to #FusaUser objects known to
+ * @manager. This list will contain all user who would ordinarily show up in the
+ * GDM face browser, as well as any other users currently logged into managed X
+ * displays. This list must be freed with g_slist_free() when no longer needed,
+ * but the list items themselves are not references and should not be released.
+ * 
+ * Returns: a singly-linked list of pointers to #FusaUser objects.
+ **/
+GSList *
+fusa_manager_list_users (FusaManager *manager)
+{
+  GSList *retval;
+
+  g_return_val_if_fail (FUSA_IS_MANAGER (manager), NULL);
+
+  retval = NULL;
+  g_hash_table_foreach (manager->users, listify_hash_values_hfunc, &retval);
+
+  return g_slist_sort (retval, (GCompareFunc) fusa_user_collate);
+}
+
+GQuark
+fusa_manager_dm_error_get_quark (void)
+{
+  static GQuark quark = 0;
+
+  if (G_UNLIKELY (quark == 0))
+    quark = g_quark_from_static_string ("fusa-manager-dm-error");
+
+  return quark;
+}
Index: gnome-panel/fusa-manager.h
===================================================================
RCS file: gnome-panel/fusa-manager.h
diff -N gnome-panel/fusa-manager.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/fusa-manager.h	29 Oct 2005 07:39:52 -0000
@@ -0,0 +1,124 @@
+/* 
+ * Fast User Switch Applet: fusa-manager.h
+ * 
+ * Copyright (C) 2005 James M. Cape <jcape@ignore-your.tv>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * Singleton object to manage FusaDisplay and FusaUser objects, and handle GDM
+ * interaction/configs.
+ */
+
+#ifndef __FUSA_MANAGER_H__
+#define __FUSA_MANAGER_H__ 1
+
+#include <gdk/gdkscreen.h>
+
+#include "fusa-display.h"
+#include "fusa-user.h"
+
+G_BEGIN_DECLS
+
+#define FUSA_TYPE_MANAGER \
+  (fusa_manager_get_type ())
+#define FUSA_MANAGER(object) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((object), FUSA_TYPE_MANAGER, FusaManager))
+#define FUSA_IS_MANAGER(object) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((object), FUSA_TYPE_MANAGER))
+
+#define FUSA_MANAGER_DM_ERROR \
+  (fusa_manager_dm_error_get_quark ())
+
+#define FUSA_MANAGER_SIGNAL_USER_ADDED \
+  "user-added"
+#define FUSA_MANAGER_SIGNAL_USER_REMOVED \
+  "user-removed"
+#define FUSA_MANAGER_SIGNAL_DISPLAY_ADDED \
+  "display-added"
+#define FUSA_MANAGER_SIGNAL_DISPLAY_REMOVED \
+  "display-removed"
+
+
+typedef enum
+{
+  FUSA_MANAGER_DM_ERROR_UNKNOWN               = -1,
+  FUSA_MANAGER_DM_ERROR_NOT_RUNNING           =  0,
+  FUSA_MANAGER_DM_ERROR_FLEXI_LIMIT_REACHED   =  1,
+  FUSA_MANAGER_DM_ERROR_X_ERROR               =  2,
+  FUSA_MANAGER_DM_ERROR_X_FAILED              =  3,
+  FUSA_MANAGER_DM_ERROR_X_LIMIT_REACHED       =  4,
+  FUSA_MANAGER_DM_ERROR_XNEST_AUTH_ERROR      =  5,
+  FUSA_MANAGER_DM_ERROR_X_NOT_FOUND           =  6,
+  FUSA_MANAGER_DM_ERROR_LOGOUT_ACTION         =  7,
+  FUSA_MANAGER_DM_ERROR_CONSOLES_UNSUPPORTED  =  8,
+  FUSA_MANAGER_DM_ERROR_INVALID_CONSOLE       =  9,
+  FUSA_MANAGER_DM_ERROR_UNKNOWN_CONFIG        =  50,
+  FUSA_MANAGER_DM_ERROR_PERMISSIONS           =  100,
+  FUSA_MANAGER_DM_ERROR_TOO_MANY_MESSAGES     =  200,
+  FUSA_MANAGER_DM_ERROR_OTHER                 = -2
+}
+FusaManagerDmError;
+
+typedef struct _FusaManager FusaManager;
+  
+typedef void (*FusaManagerDisplayCallback)      (FusaManager  *manager,
+						 FusaDisplay  *display,
+						 const GError *error,
+						 gpointer      data);
+
+GType        fusa_manager_get_type              (void) G_GNUC_CONST;
+
+FusaManager *fusa_manager_ref_default           (void);
+
+/* Users */
+GSList      *fusa_manager_list_users            (FusaManager *manager);
+FusaUser    *fusa_manager_get_user              (FusaManager *manager,
+						 const gchar *name);
+FusaUser    *fusa_manager_get_user_by_uid       (FusaManager *manager,
+						 uid_t        uid);
+
+/* Displays */
+GSList      *fusa_manager_list_displays         (FusaManager *manager);
+FusaDisplay *fusa_manager_get_display           (FusaManager *manager,
+						 const gchar *name);
+FusaDisplay *fusa_manager_get_display_by_screen (FusaManager *manager,
+						 GdkScreen   *screen);
+
+/* Methods */
+void         fusa_manager_activate_display      (FusaManager                *manager,
+						 FusaDisplay                *display,
+						 GdkScreen                  *screen,
+						 FusaManagerDisplayCallback  func,
+						 gpointer                    data,
+						 GDestroyNotify              notify);
+void         fusa_manager_new_console           (FusaManager                *manager,
+						 GdkScreen                  *screen,
+						 FusaManagerDisplayCallback  func,
+						 gpointer                    data,
+						 GDestroyNotify              notify);
+void         fusa_manager_new_xnest             (FusaManager                *manager,
+						 GdkScreen                  *screen,
+						 FusaManagerDisplayCallback  func,
+						 gpointer                    data,
+						 GDestroyNotify              notify);
+
+/* Errors */
+GQuark       fusa_manager_dm_error_get_quark    (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* !__FUSA_MANAGER_H__ */
Index: gnome-panel/fusa-user-menu-item.c
===================================================================
RCS file: gnome-panel/fusa-user-menu-item.c
diff -N gnome-panel/fusa-user-menu-item.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/fusa-user-menu-item.c	29 Oct 2005 07:39:52 -0000
@@ -0,0 +1,441 @@
+/* 
+ * Fast User Switch Applet: fusa-user-menu-item.c
+ * 
+ * Copyright (C) 2005 James M. Cape <jcape@ignore-your.tv>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <string.h>
+
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkdialog.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkiconfactory.h>
+#include <gtk/gtkicontheme.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkradiobutton.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkvbox.h>
+
+#include "fusa-user-menu-item.h"
+
+
+/* **************** *
+ *  Private Macros  *
+ * **************** */
+
+#define DEFAULT_ICON_SIZE	24
+#define CLOSE_ENOUGH_SIZE	2
+
+/* ********************** *
+ *  Private Enumerations  *
+ * ********************** */
+
+enum
+{
+  PROP_0,
+  PROP_USER,
+  PROP_ICON_SIZE
+};
+
+
+/* ******************** *
+ *  Private Structures  *
+ * ******************** */
+  
+struct _FusaUserMenuItem
+{
+  GtkImageMenuItem parent;
+
+  FusaUser *user;
+
+  GtkWidget *image;
+  GtkWidget *label;
+
+  gulong user_notify_id;
+  gulong user_icon_changed_id;
+  gulong user_displays_changed_id;
+  gint icon_size;
+};
+
+struct _FusaUserMenuItemClass
+{
+  GtkImageMenuItemClass parent_class;
+};
+
+
+/* ********************* *
+ *  Function Prototypes  *
+ * ********************* */
+
+/* GObject Functions */
+static void fusa_user_menu_item_set_property (GObject      *object,
+					      guint         param_id,
+					      const GValue *value,
+					      GParamSpec   *pspec);
+static void fusa_user_menu_item_get_property (GObject      *object,
+					      guint         param_id,
+					      GValue       *value,
+					      GParamSpec   *pspec);
+static void fusa_user_menu_item_finalize     (GObject      *object);
+
+/* FusaUser Callbacks */
+static void user_notify_cb           (GObject    *object,
+				      GParamSpec *pspec,
+				      gpointer    data);
+static void user_icon_changed_cb     (FusaUser   *user,
+				      gpointer    data);
+static void user_displays_changed_cb (FusaUser   *user,
+				      gpointer    data);
+static void user_weak_notify         (gpointer    data,
+				      GObject    *user_ptr);
+
+/* Widget Callbacks */
+static void image_style_set_cb (GtkWidget *widget,
+				GtkStyle  *old_style,
+				gpointer   data);
+static void label_style_set_cb (GtkWidget *widget,
+				GtkStyle  *old_style,
+				gpointer   data);
+
+/* Utility Functions */
+static void reset_label (FusaUserMenuItem *item);
+static void reset_icon  (FusaUserMenuItem *item);
+
+
+/* ******************* *
+ *  GType Declaration  *
+ * ******************* */
+
+G_DEFINE_TYPE (FusaUserMenuItem, fusa_user_menu_item, GTK_TYPE_IMAGE_MENU_ITEM);
+
+
+/* ***************** *
+ *  GType Functions  *
+ * ***************** */
+
+static void
+fusa_user_menu_item_class_init (FusaUserMenuItemClass *class)
+{
+  GObjectClass *gobject_class;
+
+  gobject_class = G_OBJECT_CLASS (class);
+
+  gobject_class->set_property = fusa_user_menu_item_set_property;
+  gobject_class->get_property = fusa_user_menu_item_get_property;
+  gobject_class->finalize = fusa_user_menu_item_finalize;
+
+  g_object_class_install_property (gobject_class,
+				   PROP_USER,
+				   g_param_spec_object ("user",
+							_("User"),
+							_("The user this menu item represents."),
+							FUSA_TYPE_USER,
+							(G_PARAM_READWRITE |
+							 G_PARAM_CONSTRUCT_ONLY)));
+  g_object_class_install_property (gobject_class,
+				   PROP_ICON_SIZE,
+				   g_param_spec_int ("icon-size",
+						     _("Icon Size"),
+						     _("The size of the icon to use."),
+						     12, G_MAXINT, DEFAULT_ICON_SIZE,
+						     G_PARAM_READWRITE));
+}
+
+static void
+fusa_user_menu_item_init (FusaUserMenuItem *item)
+{
+  item->icon_size = DEFAULT_ICON_SIZE;
+
+  item->image = gtk_image_new ();
+  g_signal_connect (item->image, "style-set",
+		    G_CALLBACK (image_style_set_cb), item);
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), item->image);
+  gtk_widget_show (item->image);
+
+  item->label = gtk_label_new (NULL);
+  gtk_label_set_use_markup (GTK_LABEL (item->label), TRUE);
+  gtk_misc_set_alignment (GTK_MISC (item->label), 0.0, 0.5);
+  g_signal_connect (item->label, "style-set",
+		    G_CALLBACK (label_style_set_cb), item);
+  gtk_container_add (GTK_CONTAINER (item), item->label);
+  gtk_widget_show (item->label);
+}
+
+
+/* ******************* *
+ *  GObject Functions  *
+ * ******************* */
+
+static void
+fusa_user_menu_item_set_property (GObject      *object,
+				  guint         param_id,
+				  const GValue *value,
+				  GParamSpec   *pspec)
+{
+  FusaUserMenuItem *item;
+
+  item = FUSA_USER_MENU_ITEM (object);
+
+  switch (param_id)
+    {
+    case PROP_USER:
+      item->user = g_value_get_object (value);
+      g_object_weak_ref (G_OBJECT (item->user), user_weak_notify, item);
+      item->user_notify_id =
+	g_signal_connect (item->user, "notify",
+			  G_CALLBACK (user_notify_cb), item);
+      item->user_icon_changed_id =
+	g_signal_connect (item->user, "icon-changed",
+			  G_CALLBACK (user_icon_changed_cb), item);
+      item->user_displays_changed_id =
+	g_signal_connect (item->user, "displays-changed",
+			  G_CALLBACK (user_displays_changed_cb), item);
+
+      if (gtk_widget_get_style (GTK_WIDGET (object)))
+	{
+	  reset_icon (item);
+	  reset_label (item);
+	}
+      break;
+    case PROP_ICON_SIZE:
+      fusa_user_menu_item_set_icon_size (item, g_value_get_int (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    }
+}
+
+static void
+fusa_user_menu_item_get_property (GObject    *object,
+				  guint       param_id,
+				  GValue     *value,
+				  GParamSpec *pspec)
+{
+  FusaUserMenuItem *item;
+
+  item = FUSA_USER_MENU_ITEM (object);
+
+  switch (param_id)
+    {
+    case PROP_USER:
+      g_value_set_object (value, item->user);
+      break;
+    case PROP_ICON_SIZE:
+      g_value_set_int (value, item->icon_size);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    }
+}
+
+static void
+fusa_user_menu_item_finalize (GObject *object)
+{
+  FusaUserMenuItem *item;
+
+  item = FUSA_USER_MENU_ITEM (object);
+
+  if (item->user)
+    {
+      g_signal_handler_disconnect (item->user, item->user_notify_id);
+      g_signal_handler_disconnect (item->user, item->user_icon_changed_id);
+      g_signal_handler_disconnect (item->user, item->user_displays_changed_id);
+      g_object_weak_unref (G_OBJECT (item->user), user_weak_notify, object);
+    }
+
+  if (G_OBJECT_CLASS (fusa_user_menu_item_parent_class)->finalize)
+    (*G_OBJECT_CLASS (fusa_user_menu_item_parent_class)->finalize) (object);
+}
+
+
+/* ******************** *
+ *  FusaUser Callbacks  *
+ * ******************** */
+
+static void
+user_notify_cb (GObject    *object,
+		GParamSpec *pspec,
+		gpointer    data)
+{
+  if (!pspec || !pspec->name)
+    return;
+
+  if (strcmp (pspec->name, "user-name") == 0 ||
+      strcmp (pspec->name, "display-name") == 0)
+    reset_label (data);
+}
+
+static void
+user_icon_changed_cb (FusaUser *user,
+		      gpointer  data)
+{
+  if (gtk_widget_has_screen (data))
+    reset_icon (data);
+}
+
+static void
+user_displays_changed_cb (FusaUser *user,
+			  gpointer  data)
+{
+  if (gtk_widget_has_screen (data))
+    reset_icon (data);
+
+  if (fusa_user_get_uid (user) == getuid ())
+    gtk_widget_set_sensitive (data, (fusa_user_get_n_displays (user) > 1));
+  else
+    gtk_widget_set_sensitive (data, TRUE);
+}
+
+static void
+user_weak_notify (gpointer  data,
+		  GObject  *user_ptr)
+{
+  FUSA_USER_MENU_ITEM (data)->user = NULL;
+
+  gtk_widget_destroy (data);
+}
+
+
+/* ****************** *
+ *  Widget Callbacks  *
+ * ****************** */
+
+static void
+image_style_set_cb (GtkWidget *widget,
+		    GtkStyle  *old_style,
+		    gpointer   data)
+{
+  reset_icon (data);
+}
+
+static void
+label_style_set_cb (GtkWidget *widget,
+		    GtkStyle  *old_style,
+		    gpointer   data)
+{
+  reset_label (data);
+}
+
+
+/* ******************* *
+ *  Utility Functions  *
+ * ******************* */
+
+static void
+reset_label (FusaUserMenuItem *item)
+{
+  gchar *text;
+  PangoLayout *layout;
+  gint height;
+
+  if (!item->user)
+    return;
+
+  text = g_strconcat ("<b>",
+		      fusa_user_get_display_name (item->user),
+		      "</b>\n",
+		      "<small>",
+		      fusa_user_get_user_name (item->user),
+		      "</small>",
+		      NULL);
+
+  gtk_label_set_markup (GTK_LABEL (item->label), text);
+  g_free (text);
+
+  layout = gtk_label_get_layout (GTK_LABEL (item->label));
+  pango_layout_get_pixel_size (layout, NULL, &height);
+
+  if (height > (item->icon_size + CLOSE_ENOUGH_SIZE))
+    gtk_label_set_markup (GTK_LABEL (item->label),
+			  fusa_user_get_display_name (item->user));
+}
+
+static void
+reset_icon (FusaUserMenuItem *item)
+{
+  GdkPixbuf *pixbuf;
+
+  if (!item->user || !gtk_widget_has_screen (GTK_WIDGET (item)))
+    return;
+
+  g_assert (item->icon_size != 0);
+
+  pixbuf = fusa_user_render_icon (item->user, GTK_WIDGET (item),
+				  item->icon_size,
+				  fusa_user_get_n_displays (item->user));
+  gtk_image_set_from_pixbuf (GTK_IMAGE (item->image), pixbuf);
+  g_object_unref (pixbuf);
+}
+
+
+/* ************************************************************************** *
+ *  Public API                                                                *
+ * ************************************************************************** */
+
+GtkWidget *
+fusa_user_menu_item_new (FusaUser *user)
+{
+  g_return_val_if_fail (FUSA_IS_USER (user), NULL);
+
+  return g_object_new (FUSA_TYPE_USER_MENU_ITEM, "user", user, NULL);
+}
+
+FusaUser *
+fusa_user_menu_item_get_user (FusaUserMenuItem *item)
+{
+  g_return_val_if_fail (FUSA_IS_USER_MENU_ITEM (item), NULL);
+
+  return item->user;
+}
+
+gint
+fusa_user_menu_item_get_icon_size (FusaUserMenuItem *item)
+{
+  g_return_val_if_fail (FUSA_IS_USER_MENU_ITEM (item), -1);
+
+  return item->icon_size;
+}
+
+void
+fusa_user_menu_item_set_icon_size (FusaUserMenuItem *item,
+				   gint              pixel_size)
+{
+  g_return_if_fail (FUSA_IS_USER_MENU_ITEM (item));
+  g_return_if_fail (pixel_size != 0);
+
+  if (pixel_size < 0)
+    item->icon_size = DEFAULT_ICON_SIZE;
+  else
+    item->icon_size = pixel_size;
+
+  if (gtk_widget_get_style (GTK_WIDGET (item)))
+    {
+      reset_icon (item);
+      reset_label (item);
+    }
+  g_object_notify (G_OBJECT (item), "icon-size");
+}
Index: gnome-panel/fusa-user-menu-item.h
===================================================================
RCS file: gnome-panel/fusa-user-menu-item.h
diff -N gnome-panel/fusa-user-menu-item.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/fusa-user-menu-item.h	29 Oct 2005 07:39:54 -0000
@@ -0,0 +1,62 @@
+/*
+ * Fast User Switch Applet: fusa-user-menu-item.h
+ * 
+ * Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * A menu item bound to a FusaUser.
+ */
+
+#ifndef __FUSA_USER_MENU_ITEM__
+#define __FUSA_USER_MENU_ITEM__ 1
+
+#include <gtk/gtkimagemenuitem.h>
+
+#include "fusa-user.h"
+
+G_BEGIN_DECLS
+
+#define FUSA_TYPE_USER_MENU_ITEM \
+  (fusa_user_menu_item_get_type ())
+#define FUSA_USER_MENU_ITEM(object) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((object), FUSA_TYPE_USER_MENU_ITEM, FusaUserMenuItem))
+#define FUSA_USER_MENU_ITEM_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), FUSA_TYPE_USER_MENU_ITEM, FusaUserMenuItemClass))
+#define FUSA_IS_USER_MENU_ITEM(object) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((object), FUSA_TYPE_USER_MENU_ITEM))
+#define FUSA_IS_USER_MENU_ITEM_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), FUSA_TYPE_USER_MENU_ITEM))
+#define FUSA_USER_MENU_ITEM_GET_CLASS(object) \
+  (G_TYPE_INSTANCE_GET_CLASS ((object), FUSA_TYPE_USER_MENU_ITEM, FusaUserMenuItemClass))
+
+typedef struct _FusaUserMenuItem FusaUserMenuItem;
+typedef struct _FusaUserMenuItemClass FusaUserMenuItemClass;
+
+GType      fusa_user_menu_item_get_type      (void) G_GNUC_CONST;
+
+GtkWidget *fusa_user_menu_item_new           (FusaUser         *user);
+
+FusaUser  *fusa_user_menu_item_get_user      (FusaUserMenuItem *item);
+
+gint       fusa_user_menu_item_get_icon_size (FusaUserMenuItem *item);
+void       fusa_user_menu_item_set_icon_size (FusaUserMenuItem *item,
+					      gint              pixel_size);
+
+G_END_DECLS
+
+#endif /* !__FUSA_USER_MENU_ITEM__ */
Index: gnome-panel/fusa-user-private.h
===================================================================
RCS file: gnome-panel/fusa-user-private.h
diff -N gnome-panel/fusa-user-private.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/fusa-user-private.h	29 Oct 2005 07:39:54 -0000
@@ -0,0 +1,47 @@
+/* 
+ * Fast User Switch Applet: fusa-user-private.h
+ * 
+ * Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * Private interfaces to the FusaUser object
+ */
+
+#ifndef __FUSA_USER_PRIVATE__
+#define __FUSA_USER_PRIVATE__ 1
+
+#include <pwd.h>
+
+#include "fusa-display.h"
+#include "fusa-user.h"
+
+G_BEGIN_DECLS
+
+void _fusa_user_update         (FusaUser            *user,
+				const struct passwd *pwent);
+
+void _fusa_user_add_display    (FusaUser            *user,
+				FusaDisplay         *display);
+void _fusa_user_remove_display (FusaUser            *user,
+				FusaDisplay         *display);
+
+void _fusa_user_icon_changed   (FusaUser            *user);
+
+G_END_DECLS
+
+#endif /* !__FUSA_USER_PRIVATE__ */
Index: gnome-panel/fusa-user.c
===================================================================
RCS file: gnome-panel/fusa-user.c
diff -N gnome-panel/fusa-user.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/fusa-user.c	29 Oct 2005 07:39:54 -0000
@@ -0,0 +1,773 @@
+/* 
+ * Fast User Switch Applet: fusa-user.c
+ * 
+ * Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+
+#include <string.h>
+
+#include <gtk/gtkicontheme.h>
+
+#include "fusa-manager-private.h"
+#include "fusa-user-private.h"
+
+
+/* **************** *
+ *  Private Macros  *
+ * **************** */
+
+#define FUSA_USER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), FUSA_TYPE_USER, FusaUserClass))
+#define FUSA_IS_USER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), FUSA_TYPE_USER))
+#define FUSA_USER_GET_CLASS(object) \
+  (G_TYPE_INSTANCE_GET_CLASS ((object), FUSA_TYPE_USER, FusaUserClass))
+
+
+/* ********************** *
+ *  Private Enumerations  *
+ * ********************** */
+
+enum
+{
+  PROP_0,
+  PROP_MANAGER,
+  PROP_DISPLAY_NAME,
+  PROP_USER_NAME,
+  PROP_UID,
+  PROP_HOME_DIR,
+  PROP_SHELL,
+  PROP_DISPLAYS
+};
+
+enum
+{
+  ICON_CHANGED,
+  DISPLAYS_CHANGED,
+  LAST_SIGNAL
+};
+
+
+/* ******************** *
+ *  Private Structures  *
+ * ******************** */
+
+struct _FusaUser
+{
+  GObject parent;
+
+  FusaManager *manager;
+
+  uid_t uid;
+  gchar *username;
+  gchar *display_name;
+  gchar *home_dir;
+  gchar *shell;
+
+  GSList *displays;
+};
+
+typedef struct _FusaUserClass
+{
+  GObjectClass parent_class;
+
+  void (*icon_changed)     (FusaUser *user);
+  void (*displays_changed) (FusaUser *user);
+}
+FusaUserClass;
+
+
+/* ********************* *
+ *  Function Prototypes  *
+ * ********************* */
+
+/* GObject Functions */
+static void fusa_user_set_property (GObject      *object,
+				    guint         param_id,
+				    const GValue *value,
+				    GParamSpec   *pspec);
+static void fusa_user_get_property (GObject      *object,
+				    guint         param_id,
+				    GValue       *value,
+				    GParamSpec   *pspec);
+static void fusa_user_finalize     (GObject      *object);
+
+/* Utility Functions */
+static void display_weak_notify (gpointer  data,
+				 GObject  *old_display);
+
+
+/* ****************** *
+ *  Global Variables  *
+ * ****************** */
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+
+/* ******************* *
+ *  GType Declaration  *
+ * ******************* */
+
+G_DEFINE_TYPE (FusaUser, fusa_user, G_TYPE_OBJECT);
+
+
+/* ********************** *
+ *  GType Initialization  *
+ * ********************** */
+
+static void
+fusa_user_class_init (FusaUserClass *class)
+{
+  GObjectClass *gobject_class;
+
+  gobject_class = G_OBJECT_CLASS (class);
+
+  gobject_class->set_property = fusa_user_set_property;
+  gobject_class->get_property = fusa_user_get_property;
+  gobject_class->finalize = fusa_user_finalize;
+
+  g_object_class_install_property (gobject_class,
+				   PROP_MANAGER,
+				   g_param_spec_object ("manager",
+							_("Manager"),
+							_("The user manager object this user is controlled by."),
+							FUSA_TYPE_MANAGER,
+							(G_PARAM_READWRITE |
+							 G_PARAM_CONSTRUCT_ONLY)));
+
+  g_object_class_install_property (gobject_class,
+				   PROP_DISPLAY_NAME,
+				   g_param_spec_string ("display-name",
+							"Display Name",
+							"The user-friendly name to display for this user.",
+							NULL,
+							G_PARAM_READABLE));
+
+  g_object_class_install_property (gobject_class,
+				   PROP_UID,
+				   g_param_spec_ulong ("uid",
+						       "User ID",
+						       "The UID for this user.",
+						       0, G_MAXULONG, 0,
+						       G_PARAM_READABLE));
+  g_object_class_install_property (gobject_class,
+				   PROP_USER_NAME,
+				   g_param_spec_string ("user-name",
+							"User Name",
+							"The login name for this user.",
+							NULL,
+							G_PARAM_READABLE));
+  g_object_class_install_property (gobject_class,
+				   PROP_HOME_DIR,
+				   g_param_spec_string ("home-directory",
+							"Home Directory",
+							"The home directory for this user.",
+							NULL,
+							G_PARAM_READABLE));
+  g_object_class_install_property (gobject_class,
+				   PROP_SHELL,
+				   g_param_spec_string ("shell",
+							"Shell",
+							"The shell for this user.",
+							NULL,
+							G_PARAM_READABLE));
+
+  signals[ICON_CHANGED] =
+    g_signal_new ("icon-changed",
+		  G_TYPE_FROM_CLASS (class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (FusaUserClass, icon_changed),
+		  NULL, NULL,
+		  g_cclosure_marshal_VOID__VOID,
+		  G_TYPE_NONE, 0);
+  signals[DISPLAYS_CHANGED] =
+    g_signal_new ("displays-changed",
+		  G_TYPE_FROM_CLASS (class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (FusaUserClass, displays_changed),
+		  NULL, NULL,
+		  g_cclosure_marshal_VOID__VOID,
+		  G_TYPE_NONE, 0);
+}
+
+static void
+fusa_user_init (FusaUser *user)
+{
+  user->manager = NULL;
+  user->username = NULL;
+  user->display_name = NULL;
+  user->displays = NULL;
+}
+
+
+/* ******************* *
+ *  GObject Functions  *
+ * ******************* */
+
+static void
+fusa_user_set_property (GObject      *object,
+			guint         param_id,
+			const GValue *value,
+			GParamSpec   *pspec)
+{
+  FusaUser *user;
+
+  user = FUSA_USER (object);
+
+  switch (param_id)
+    {
+    case PROP_MANAGER:
+      user->manager = g_value_get_object (value);
+      g_assert (user->manager);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    }
+}
+
+static void
+fusa_user_get_property (GObject    *object,
+			guint       param_id,
+			GValue     *value,
+			GParamSpec *pspec)
+{
+  FusaUser *user;
+
+  user = FUSA_USER (object);
+
+  switch (param_id)
+    {
+    case PROP_MANAGER:
+      g_value_set_object (value, user->manager);
+      break;
+    case PROP_USER_NAME:
+      g_value_set_string (value, user->username);
+      break;
+    case PROP_DISPLAY_NAME:
+      g_value_set_string (value, user->display_name);
+      break;
+    case PROP_HOME_DIR:
+      g_value_set_string (value, user->home_dir);
+      break;
+    case PROP_UID:
+      g_value_set_ulong (value, user->uid);
+      break;
+    case PROP_SHELL:
+      g_value_set_string (value, user->shell);
+      break;
+    case PROP_DISPLAYS:
+      if (user->displays)
+	{
+	  GValueArray *ar;
+	  GSList *list;
+	  GValue tmp = { 0 };
+
+	  ar = g_value_array_new (g_slist_length (user->displays));
+	  g_value_init (&tmp, FUSA_TYPE_DISPLAY);
+	  for (list = user->displays; list; list = list->next)
+	    {
+	      g_value_set_object (&tmp, list->data);
+	      g_value_array_append (ar, &tmp);
+	      g_value_reset (&tmp);
+	    }
+
+	  g_value_take_boxed (value, ar);
+	}
+      else
+	g_value_set_boxed (value, NULL);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    }
+}
+
+static void
+fusa_user_finalize (GObject *object)
+{
+  FusaUser *user;
+
+  user = FUSA_USER (object);
+
+  g_free (user->username);
+  g_free (user->display_name);
+
+  while (user->displays)
+    {
+      g_object_weak_unref (G_OBJECT (user->displays->data), display_weak_notify,
+			   user);
+      user->displays = g_slist_delete_link (user->displays, user->displays);
+    }
+
+
+  if (G_OBJECT_CLASS (fusa_user_parent_class)->finalize)
+    (*G_OBJECT_CLASS (fusa_user_parent_class)->finalize) (object);
+}
+
+/* ******************* *
+ *  Utility Functions  *
+ * ******************* */
+
+static void
+display_weak_notify (gpointer  user_data,
+		     GObject  *old_display)
+{
+  FusaUser *user;
+
+  user = FUSA_USER (user_data);
+
+  user->displays = g_slist_delete_link (user->displays,
+					g_slist_find (user->displays,
+						      old_display));
+  g_signal_emit (user, signals[DISPLAYS_CHANGED], 0);
+}
+
+
+/* ************************************************************************** *
+ *  Semi-Private API                                                          *
+ * ************************************************************************** */
+
+/**
+ * _fusa_user_update:
+ * @user: the user object to update.
+ * @pwent: the user data to use.
+ * 
+ * Updates the properties of @user using the data in @pwent.
+ * 
+ * Since: 1.0
+ **/
+void
+_fusa_user_update (FusaUser            *user,
+		   const struct passwd *pwent)
+{
+  gchar *display_name;
+
+  g_return_if_fail (FUSA_IS_USER (user));
+  g_return_if_fail (pwent != NULL);
+
+  g_object_freeze_notify (G_OBJECT (user));
+
+  /* Display Name */
+  if (pwent->pw_gecos && pwent->pw_gecos[0] != '\0')
+    {
+      gchar *first_comma;
+
+      first_comma = strchr (pwent->pw_gecos, ',');
+      if (first_comma)
+	display_name = g_strndup (pwent->pw_gecos,
+				  (first_comma - pwent->pw_gecos));
+      else
+	display_name = g_strdup (pwent->pw_gecos);
+
+      if (display_name[0] == '\0')
+	{
+	  g_free (display_name);
+	  display_name = NULL;
+	}
+    }
+  else
+    display_name = NULL;
+
+  if ((display_name && !user->display_name) ||
+      (!display_name && user->display_name) ||
+      (display_name &&
+       user->display_name &&
+       strcmp (display_name, user->display_name) != 0))
+    {
+      g_free (user->display_name);
+      user->display_name = display_name;
+      g_object_notify (G_OBJECT (user), "display-name");
+    }
+  else
+    g_free (display_name);
+
+  /* UID */
+  if (pwent->pw_uid != user->uid)
+    {
+      user->uid = pwent->pw_uid;
+      g_object_notify (G_OBJECT (user), "uid");
+    }
+
+  /* Username */
+  if ((pwent->pw_name && !user->username) ||
+      (!pwent->pw_name && user->username) ||
+      (pwent->pw_name &&
+       user->username &&
+       strcmp (user->username, pwent->pw_name) != 0))
+    {
+      g_free (user->username);
+      user->username = g_strdup (pwent->pw_name);
+      g_object_notify (G_OBJECT (user), "user-name");
+    }
+
+  /* Home Directory */
+  if ((pwent->pw_dir && !user->home_dir) ||
+      (!pwent->pw_dir && user->home_dir) ||
+      strcmp (user->home_dir, pwent->pw_dir) != 0)
+    {
+      g_free (user->home_dir);
+      user->home_dir = g_strdup (pwent->pw_dir);
+      g_object_notify (G_OBJECT (user), "home-directory");
+      g_signal_emit (user, signals[ICON_CHANGED], 0);
+    }
+
+  /* Shell */
+  if ((pwent->pw_shell && !user->shell) ||
+      (!pwent->pw_shell && user->shell) ||
+      (pwent->pw_shell &&
+       user->shell &&
+       strcmp (user->shell, pwent->pw_shell) != 0))
+    {
+      g_free (user->shell);
+      user->shell = g_strdup (pwent->pw_shell);
+      g_object_notify (G_OBJECT (user), "shell");
+    }
+
+  g_object_thaw_notify (G_OBJECT (user));
+}
+
+/**
+ * _fusa_user_add_display:
+ * @user: the user to modify.
+ * @display: the virtual terminal to add.
+ * 
+ * Adds @display to the list of terminals @user is logged in on, and emits the
+ * "displays-changed" signal, if necessary.
+ * 
+ * Since: 1.0
+ **/
+void
+_fusa_user_add_display (FusaUser *user,
+		        FusaDisplay   *display)
+{
+  g_return_if_fail (FUSA_IS_USER (user));
+  g_return_if_fail (FUSA_IS_DISPLAY (display));
+
+  if (!g_slist_find (user->displays, display))
+    {
+      g_object_weak_ref (G_OBJECT (display), display_weak_notify, user);
+      user->displays = g_slist_append (user->displays, display);
+      g_signal_emit (user, signals[DISPLAYS_CHANGED], 0);
+    }
+}
+
+/**
+ * _fusa_user_remove_display:
+ * @user: the user to modify.
+ * @display: the virtual terminal to remove.
+ * 
+ * Removes @display from the list of terminals @user is logged in on, and emits the
+ * "displays-changed" signal, if necessary.
+ * 
+ * Since: 1.0
+ **/
+void
+_fusa_user_remove_display (FusaUser *user,
+			   FusaDisplay   *display)
+{
+  GSList *li;
+
+  g_return_if_fail (FUSA_IS_USER (user));
+  g_return_if_fail (FUSA_IS_DISPLAY (display));
+
+  li = g_slist_find (user->displays, display);
+  if (li)
+    {
+      g_object_weak_unref (G_OBJECT (display), display_weak_notify, user);
+      user->displays = g_slist_delete_link (user->displays, li);
+      g_signal_emit (user, signals[DISPLAYS_CHANGED], 0);
+    }
+}
+
+/**
+ * _fusa_user_icon_changed:
+ * @user: the user to emit the signal for.
+ * 
+ * Emits the "icon-changed" signal for @user.
+ * 
+ * Since: 1.0
+ **/
+void
+_fusa_user_icon_changed (FusaUser *user)
+{
+  g_return_if_fail (FUSA_IS_USER (user));
+
+  g_signal_emit (user, signals[ICON_CHANGED], 0);
+}
+
+
+/* ************************************************************************** *
+ *  Public API                                                                *
+ * ************************************************************************** */
+
+/**
+ * fusa_user_get_uid:
+ * @user: the user object to examine.
+ * 
+ * Retrieves the ID of @user.
+ * 
+ * Returns: a pointer to an array of characters which must not be modified or
+ *  freed, or %NULL.
+ * 
+ * Since: 1.0
+ **/
+
+uid_t
+fusa_user_get_uid (FusaUser *user)
+{
+  g_return_val_if_fail (FUSA_IS_USER (user), -1);
+
+  return user->uid;
+}
+
+/**
+ * fusa_user_get_display_name:
+ * @user: the user object to examine.
+ * 
+ * Retrieves the display name of @user.
+ * 
+ * Returns: a pointer to an array of characters which must not be modified or
+ *  freed, or %NULL.
+ * 
+ * Since: 1.0
+ **/
+G_CONST_RETURN gchar *
+fusa_user_get_display_name (FusaUser *user)
+{
+  g_return_val_if_fail (FUSA_IS_USER (user), NULL);
+
+  return (user->display_name ? user->display_name : user->username);
+}
+
+/**
+ * fusa_user_get_user_name:
+ * @user: the user object to examine.
+ * 
+ * Retrieves the login name of @user.
+ * 
+ * Returns: a pointer to an array of characters which must not be modified or
+ *  freed, or %NULL.
+ * 
+ * Since: 1.0
+ **/
+
+G_CONST_RETURN gchar *
+fusa_user_get_user_name (FusaUser *user)
+{
+  g_return_val_if_fail (FUSA_IS_USER (user), NULL);
+
+  return user->username;
+}
+
+/**
+ * fusa_user_get_home_directory:
+ * @user: the user object to examine.
+ * 
+ * Retrieves the home directory of @user.
+ * 
+ * Returns: a pointer to an array of characters which must not be modified or
+ *  freed, or %NULL.
+ * 
+ * Since: 1.0
+ **/
+
+G_CONST_RETURN gchar *
+fusa_user_get_home_directory (FusaUser *user)
+{
+  g_return_val_if_fail (FUSA_IS_USER (user), NULL);
+
+  return user->home_dir;
+}
+
+/**
+ * fusa_user_get_shell:
+ * @user: the user object to examine.
+ * 
+ * Retrieves the login shell of @user.
+ * 
+ * Returns: a pointer to an array of characters which must not be modified or
+ *  freed, or %NULL.
+ * 
+ * Since: 1.0
+ **/
+
+G_CONST_RETURN gchar *
+fusa_user_get_shell (FusaUser *user)
+{
+  g_return_val_if_fail (FUSA_IS_USER (user), NULL);
+
+  return user->shell;
+}
+
+/**
+ * fusa_user_get_displays:
+ * @user: the user object to examine.
+ * 
+ * Retrieves a new list of the displays that @user is logged in on. The list
+ * itself must be freed with g_slist_free() when no longer needed.
+ * 
+ * Returns: a list of #FusaDisplay objects which must be freed with
+ *  g_slist_free().
+ * 
+ * Since: 1.0
+ **/
+GSList *
+fusa_user_get_displays (FusaUser *user)
+{
+  g_return_val_if_fail (FUSA_IS_USER (user), NULL);
+
+  return g_slist_copy (user->displays);
+}
+
+/**
+ * fusa_user_get_n_displays:
+ * @user: the user object to examine.
+ * 
+ * Retrieves the number of displays that @user is logged into.
+ * 
+ * Returns: an unsigned integer.
+ * 
+ * Since: 1.0
+ **/
+guint
+fusa_user_get_n_displays (FusaUser *user)
+{
+  g_return_val_if_fail (FUSA_IS_USER (user), FALSE);
+
+  return g_slist_length (user->displays);
+}
+
+/**
+ * fusa_user_render_icon:
+ * @user: the user object to examine.
+ * @widget: the intended destination widget.
+ * @icon_size: the desired icon size.
+ * @error: a pointer to a location to store errors while reading the icon.
+ * 
+ * Loads a copy of the face pixbuf for @user at @icon_size. If an error occurs,
+ * %NULL will be returned and @error will be set, if @error is not %NULL.
+ * 
+ * Returns: a newly allocated #GdkPixbuf object, or %NULL.
+ * 
+ * Since: 1.0
+ **/
+GdkPixbuf *
+fusa_user_render_icon (FusaUser  *user,
+		       GtkWidget *widget,
+		       gint       icon_size,
+		       gboolean   desktop_overlay)
+{
+  GdkPixbuf *pixbuf;
+
+  g_return_val_if_fail (FUSA_IS_USER (user), NULL);
+  g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), NULL);
+  g_return_val_if_fail (icon_size > 0, NULL);
+
+  pixbuf = _fusa_manager_render_icon (user->manager, user, widget, icon_size);
+
+  if (desktop_overlay)
+    {
+      GtkIconTheme *theme;
+      GdkPixbuf *overlay;
+    
+      if (gtk_widget_has_screen (widget))
+	theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
+      else
+	theme = gtk_icon_theme_get_default ();
+
+      overlay = gtk_icon_theme_load_icon (theme, "gnome-fs-desktop",
+					  icon_size / 2, 0, NULL);
+
+      if (overlay)
+	{
+	  gint pixbuf_width, pixbuf_height, overlay_width, overlay_height;
+	  GdkPixbuf *tmp;
+
+	  pixbuf_width = gdk_pixbuf_get_width (pixbuf);
+	  pixbuf_height = gdk_pixbuf_get_height (pixbuf);
+
+	  tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+				pixbuf_width, pixbuf_height);
+	  gdk_pixbuf_fill (tmp, 0x00000000);
+
+	  overlay_width = gdk_pixbuf_get_width (overlay);
+	  overlay_height = gdk_pixbuf_get_height (overlay);
+
+	  gdk_pixbuf_copy_area (overlay, 0, 0, overlay_width, overlay_height,
+				tmp, (pixbuf_width - overlay_width),
+				(pixbuf_height - overlay_height));
+	  g_object_unref (overlay);
+
+	  gdk_pixbuf_composite (tmp, pixbuf,
+				pixbuf_width - overlay_width,
+				pixbuf_height - overlay_height,
+				overlay_width, overlay_height,
+				0.0, 0.0, 1.0, 1.0,
+				GDK_INTERP_BILINEAR, 255);
+	  g_object_unref (tmp);
+	}
+    }
+
+  return pixbuf;
+}
+
+gint
+fusa_user_collate (FusaUser *user1,
+		   FusaUser *user2)
+{
+  const gchar *str1, *str2;
+
+  g_return_val_if_fail (user1 == NULL || FUSA_IS_USER (user1), 0);
+  g_return_val_if_fail (user2 == NULL || FUSA_IS_USER (user2), 0);
+
+  if (!user1 && user2)
+    return -1;
+
+  if (user1 && !user2)
+    return 1;
+
+  if (!user1 && !user2)
+    return 0;
+
+  if (user1->display_name)
+    str1 = user1->display_name;
+  else
+    str1 = user1->username;
+
+  if (user2->display_name)
+    str2 = user2->display_name;
+  else
+    str2 = user2->username;
+
+  if (!str1 && str2)
+    return -1;
+
+  if (str1 && !str2)
+    return 1;
+
+  if (!str1 && !str2)
+    return 0;
+
+  return g_utf8_collate (str1, str2);
+}
Index: gnome-panel/fusa-user.h
===================================================================
RCS file: gnome-panel/fusa-user.h
diff -N gnome-panel/fusa-user.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/fusa-user.h	29 Oct 2005 07:39:54 -0000
@@ -0,0 +1,64 @@
+/* 
+ * Fast User Switch Applet: fusa-user.h
+ * 
+ * Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * Facade object for user data, owned by FusaUserManager and updated by FusaVt
+ */
+
+#ifndef __FUSA_USER__
+#define __FUSA_USER__ 1
+
+#include <sys/types.h>
+#include <gtk/gtkwidget.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+G_BEGIN_DECLS
+
+#define FUSA_TYPE_USER \
+  (fusa_user_get_type ())
+#define FUSA_USER(object) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((object), FUSA_TYPE_USER, FusaUser))
+#define FUSA_IS_USER(object) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((object), FUSA_TYPE_USER))
+
+typedef struct _FusaUser FusaUser;
+
+GType                 fusa_user_get_type         (void) G_GNUC_CONST;
+
+uid_t		      fusa_user_get_uid            (FusaUser   *user);
+G_CONST_RETURN gchar *fusa_user_get_user_name      (FusaUser   *user);
+G_CONST_RETURN gchar *fusa_user_get_display_name   (FusaUser   *user);
+G_CONST_RETURN gchar *fusa_user_get_home_directory (FusaUser   *user);
+G_CONST_RETURN gchar *fusa_user_get_shell          (FusaUser   *user);
+
+GSList               *fusa_user_get_displays       (FusaUser   *user);
+guint                 fusa_user_get_n_displays     (FusaUser   *user);
+
+GdkPixbuf            *fusa_user_render_icon        (FusaUser   *user,
+						    GtkWidget  *widget,
+						    gint        icon_size,
+						    gboolean    desktop_overlay);
+
+gint                  fusa_user_collate            (FusaUser   *user1,
+						    FusaUser   *user2);
+
+G_END_DECLS
+
+#endif /* !__FUSA_USER_MENU_ITEM__ */
Index: gnome-panel/fusa-utils.c
===================================================================
RCS file: gnome-panel/fusa-utils.c
diff -N gnome-panel/fusa-utils.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/fusa-utils.c	29 Oct 2005 07:39:54 -0000
@@ -0,0 +1,99 @@
+/* 
+ * Fast User Switch Applet: fusa-vt-manager.h
+ * 
+ * Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include <gtk/gtkalignment.h>
+#include <gtk/gtkexpander.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkmessagedialog.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkvbox.h>
+
+#include "fusa-utils.h"
+
+
+void
+fusa_dialog_hig_fix (GtkWidget *dialog,
+		     GtkStyle  *old_style,
+		     gpointer   is_message_esque)
+{
+  gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
+  gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (dialog)->action_area),
+				  0);
+  gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->action_area), 6);
+  gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), 0);
+
+  if (is_message_esque)
+    gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 24);
+  else
+    gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 12);
+}
+
+void
+fusa_die_an_ignominious_death (const gchar *primary_markup,
+			       const gchar *secondary_markup,
+			       const gchar *details_text)
+{
+  GtkWidget *dialog;
+
+  GDK_THREADS_ENTER ();
+
+  dialog = gtk_message_dialog_new (NULL, 0,
+				   GTK_MESSAGE_ERROR,
+				   GTK_BUTTONS_NONE,
+				   primary_markup);
+  gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
+					      secondary_markup);
+  gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_QUIT,
+				     GTK_RESPONSE_CLOSE);
+
+  if (details_text)
+    {
+      GtkWidget *expander, *alignment, *label;
+    
+      alignment = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
+      gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 12, 12, 0, 12);
+      gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
+			  alignment, TRUE, TRUE, 0);
+      gtk_widget_show (alignment);
+
+      expander = gtk_expander_new (_("Show Details"));
+      gtk_container_add (GTK_CONTAINER (alignment), expander);
+      gtk_widget_show (expander);
+
+      alignment = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
+      gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 12, 0);
+      gtk_container_add (GTK_CONTAINER (expander), alignment);
+      gtk_widget_show (alignment);
+
+      label = gtk_label_new (details_text);
+      gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+      gtk_container_add (GTK_CONTAINER (alignment), label);
+      gtk_widget_show (label);
+    }
+
+  gtk_dialog_run (GTK_DIALOG (dialog));
+  gtk_main_quit ();
+
+  GDK_THREADS_LEAVE ();
+}
Index: gnome-panel/fusa-utils.h
===================================================================
RCS file: gnome-panel/fusa-utils.h
diff -N gnome-panel/fusa-utils.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/fusa-utils.h	29 Oct 2005 07:39:54 -0000
@@ -0,0 +1,37 @@
+/* 
+ * Fast User Switch Applet: fusa-utils.h
+ * 
+ * Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __FUSA_UTILS_H__
+#define __FUSA_UTILS_H__ 1
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+void fusa_dialog_hig_fix           (GtkWidget   *dialog,
+				    GtkStyle    *old_style,
+				    gpointer     is_message_esque);
+void fusa_die_an_ignominious_death (const gchar *primary_markup,
+				    const gchar *secondary_markup,
+				    const gchar *details_text);
+
+G_END_DECLS
+
+#endif /* !__FUSA_UTILS_H__ */
Index: gnome-panel/gdmcomm.c
===================================================================
RCS file: gnome-panel/gdmcomm.c
diff -N gnome-panel/gdmcomm.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/gdmcomm.c	29 Oct 2005 07:39:55 -0000
@@ -0,0 +1,510 @@
+/*
+ *    GDMcommunication routines
+ *    (c)2001 Queen of England, (c)2002,2003 George Lebl
+ *    (c)2005 James M. Cape.
+ *    
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU 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 General Public License for more details.
+ *   
+ *    You should have received a copy of the GNU General Public License
+ *    along with this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <errno.h>
+
+#include <X11/Xauth.h>
+
+#include <gdk/gdkdisplay.h>
+
+#include <glib/gi18n.h>
+
+#include "gdmcomm.h"
+
+
+/* ***************************************************************** *
+ *  From gdm2/vicious-extensions/ve-misc.h                           *
+ *  Copyright (c) 2000 Eazel, Inc., (c) 2001,2002 George Lebl        *
+ * ***************************************************************** */
+
+#define VE_IGNORE_EINTR(expr) \
+  do {		\
+    errno = 0;	\
+    expr;		\
+  } while G_UNLIKELY (errno == EINTR);
+
+
+static const gboolean debug = FALSE;
+
+static char *
+do_command (int         fd,
+	    const char *command,
+	    gboolean    get_response)
+{
+	GString *str;
+	char buf[1];
+	char *cstr;
+	int ret;
+#ifndef MSG_NOSIGNAL
+	void (*old_handler)(int);
+#endif
+
+	if (debug)
+		g_print ("Sending command: '%s'\n", command);
+
+	cstr = g_strdup_printf ("%s\n", command);
+
+#ifdef MSG_NOSIGNAL
+	ret = send (fd, cstr, strlen (cstr), MSG_NOSIGNAL);
+#else
+	old_handler = signal (SIGPIPE, SIG_IGN);
+	ret = send (fd, cstr, strlen (cstr), 0);
+	signal (SIGPIPE, old_handler);
+#endif
+	g_free (cstr);
+
+	if (ret < 0)
+		return NULL;
+
+	if ( ! get_response)
+		return NULL;
+
+	str = g_string_sized_new (10);
+	while (read (fd, buf, 1) == 1 &&
+	       buf[0] != '\n') {
+		g_string_append_c (str, buf[0]);
+	}
+
+	if (debug)
+		g_print ("  Got response: '%s'\n", str->str);
+
+	cstr = str->str;
+	g_string_free (str, FALSE);
+	return cstr;
+}
+
+static gboolean
+version_ok_p (const char *version,
+	      const char *min_version)
+{
+	int a = 0, b = 0, c = 0, d = 0;
+	int mina = 0, minb = 0, minc = 0, mind = 0;
+
+	/* Note that if some fields in the version don't exist, then
+	 * we don't mind, they are zero */
+	sscanf (version, "%d.%d.%d.%d", &a, &b, &c, &d);
+	sscanf (min_version, "%d.%d.%d.%d", &mina, &minb, &minc, &mind);
+
+	if ((a > mina) ||
+	    (a == mina && b > minb) ||
+	    (a == mina && b == minb && c > minc) ||
+	    (a == mina && b == minb && c == minc && d >= mind))
+		return TRUE;
+	else
+		return FALSE;
+}
+
+char *
+gdmcomm_call_gdm (const char *command,
+		  const char *auth_cookie,
+		  const char *min_version,
+		  int         tries)
+{
+	struct sockaddr_un addr;
+	int fd;
+	char *ret;
+
+	if (tries <= 0)
+		return NULL;
+
+	fd = socket (AF_UNIX, SOCK_STREAM, 0);
+	if (fd < 0) {
+		return gdmcomm_call_gdm (command, auth_cookie, min_version, tries - 1);
+	}
+
+	strcpy (addr.sun_path, GDM_SUP_SOCKET);
+	addr.sun_family = AF_UNIX;
+
+	if (connect (fd, (struct sockaddr *)&addr, sizeof (addr)) < 0) {
+		VE_IGNORE_EINTR (close (fd));
+		return gdmcomm_call_gdm (command, auth_cookie, min_version, tries - 1);
+	}
+
+	/* Version check first */
+	ret = do_command (fd, GDM_SUP_VERSION, TRUE /* get_response */);
+	if (ret == NULL) {
+		VE_IGNORE_EINTR (close (fd));
+		return gdmcomm_call_gdm (command, auth_cookie, min_version, tries - 1);
+	}
+	if (strncmp (ret, "GDM ", strlen ("GDM ")) != 0) {
+		g_free (ret);
+		VE_IGNORE_EINTR (close (fd));
+		return NULL;
+	}
+	if ( ! version_ok_p (&ret[4], min_version)) {
+		g_free (ret);
+		do_command (fd, GDM_SUP_CLOSE, FALSE /* get_response */);
+		VE_IGNORE_EINTR (close (fd));
+		return NULL;
+	}
+	g_free (ret);
+
+	/* require authentication */
+	if (auth_cookie != NULL)  {
+		char *auth_cmd = g_strdup_printf
+			(GDM_SUP_AUTH_LOCAL " %s", auth_cookie);
+		ret = do_command (fd, auth_cmd, TRUE /* get_response */);
+		g_free (auth_cmd);
+		if (ret == NULL) {
+			VE_IGNORE_EINTR (close (fd));
+			return gdmcomm_call_gdm (command, auth_cookie,
+						 min_version, tries - 1);
+		}
+		/* not auth'ed */
+		if (strcmp (ret, "OK") != 0) {
+			do_command (fd, GDM_SUP_CLOSE,
+				    FALSE /* get_response */);
+			VE_IGNORE_EINTR (close (fd));
+			/* returns the error */
+			return ret;
+		}
+		g_free (ret);
+	}
+
+	ret = do_command (fd, command, TRUE /* get_response */);
+	if (ret == NULL) {
+		VE_IGNORE_EINTR (close (fd));
+		return gdmcomm_call_gdm (command, auth_cookie, min_version, tries - 1);
+	}
+
+	do_command (fd, GDM_SUP_CLOSE, FALSE /* get_response */);
+
+	VE_IGNORE_EINTR (close (fd));
+
+	return ret;
+}
+
+static gchar *
+get_dispnum (GdkScreen *screen)
+{
+  gchar *tmp, *retval;
+
+  tmp = g_strdup (gdk_display_get_name (gdk_screen_get_display (screen)));
+  if (tmp)
+    {
+      gchar *ptr;
+
+      ptr = strchr (tmp, '.');
+      if (ptr)
+	*ptr = '\0';
+
+      ptr = strchr (tmp, ':');
+      if (ptr)
+	{
+	  while (*ptr == ':')
+	    ptr++;
+
+	  retval = g_strdup (ptr);
+	}
+      else
+	retval = g_strdup (tmp);
+
+      g_free (tmp);
+    }
+  else
+    retval = g_strdup ("0");
+
+  return retval;
+}
+
+/* This just gets a cookie of MIT-MAGIC-COOKIE-1 type */
+gchar *
+gdmcomm_get_a_cookie (GdkScreen *screen,
+		      gboolean   binary)
+{
+	FILE *fp;
+	char *number;
+	char *cookie = NULL;
+	Xauth *xau;
+
+	VE_IGNORE_EINTR (fp = fopen (XauFileName (), "r"));
+	if (fp == NULL) {
+		return NULL;
+	}
+
+	number = get_dispnum (screen);
+
+	cookie = NULL;
+
+	while ((xau = XauReadAuth (fp)) != NULL) {
+		/* Just find the FIRST magic cookie, that's what gdm uses */
+		if (xau->number_length != strlen (number) ||
+		    strncmp (xau->number, number, xau->number_length) != 0 ||
+		    /* gdm sends MIT-MAGIC-COOKIE-1 cookies of length 16,
+		     * so just do those */
+		    xau->data_length != 16 ||
+		    xau->name_length != strlen ("MIT-MAGIC-COOKIE-1") ||
+		    strncmp (xau->name, "MIT-MAGIC-COOKIE-1",
+			     xau->name_length) != 0) {
+			XauDisposeAuth (xau);
+			continue;
+		}
+
+		if (binary) {
+			cookie = g_new0 (char, 16);
+			memcpy (cookie, xau->data, 16);
+		} else {
+			int i;
+			GString *str;
+
+			str = g_string_new (NULL);
+
+			for (i = 0; i < xau->data_length; i++) {
+				g_string_append_printf
+					(str, "%02x",
+					 (guint)(guchar)xau->data[i]);
+			}
+			cookie = g_string_free (str, FALSE);
+		}
+
+		XauDisposeAuth (xau);
+
+		break;
+	}
+	VE_IGNORE_EINTR (fclose (fp));
+
+	return cookie;
+}
+
+const char *
+gdmcomm_get_auth_cookie (GdkScreen *screen)
+{
+	FILE *fp;
+	char *number;
+	static gboolean tried = FALSE;
+	static char *cookie = NULL;
+	Xauth *xau;
+
+	if (tried)
+		return cookie;
+
+	VE_IGNORE_EINTR (fp = fopen (XauFileName (), "r"));
+	if (fp == NULL) {
+		cookie = NULL;
+		tried = TRUE;
+		return NULL;
+	}
+
+	number = get_dispnum (screen);
+
+	cookie = NULL;
+
+	while ((xau = XauReadAuth (fp)) != NULL) {
+		char *cmd;
+		char *ret;
+		int i;
+		char buffer[40 /* 2*16 == 32, so 40 is enough */];
+
+		/* Only Family local things are considered, all console
+		 * logins DO have this family (and even some local xdmcp
+		 * logins, though those will not pass by gdm itself of
+		 * course) */
+		if (xau->family != FamilyLocal ||
+		    xau->number_length != strlen (number) ||
+		    strncmp (xau->number, number, xau->number_length) != 0 ||
+		    /* gdm sends MIT-MAGIC-COOKIE-1 cookies of length 16,
+		     * so just do those */
+		    xau->data_length != 16 ||
+		    xau->name_length != strlen ("MIT-MAGIC-COOKIE-1") ||
+		    strncmp (xau->name, "MIT-MAGIC-COOKIE-1",
+			     xau->name_length) != 0 ||
+		    xau->data_length != 16) {
+			XauDisposeAuth (xau);
+			continue;
+		}
+
+		buffer[0] = '\0';
+		for (i = 0; i < 16; i++) {
+			char sub[3];
+			g_snprintf (sub, sizeof (sub), "%02x",
+				    (guint)(guchar)xau->data[i]);
+			strcat (buffer, sub);
+		}
+
+		XauDisposeAuth (xau);
+
+		cmd = g_strdup_printf (GDM_SUP_AUTH_LOCAL " %s", buffer);
+		ret = gdmcomm_call_gdm (cmd, NULL /* auth cookie */, "2.2.4.0", 5);
+		if (ret != NULL &&
+		    strcmp (ret, "OK") == 0) {
+			g_free (ret);
+			cookie = g_strdup (buffer);
+			break;
+		}
+		g_free (ret);
+	}
+	VE_IGNORE_EINTR (fclose (fp));
+      
+	g_free (number);
+
+	tried = TRUE;
+	return cookie;
+}
+
+#if 0
+gboolean
+gdmcomm_check (gboolean gui_bitching)
+{
+	GtkWidget *dialog;
+	FILE *fp = NULL;
+	long pid;
+	char *pidfile;
+	struct stat s;
+	int statret;
+
+	pidfile = ve_config_get_string (ve_config_get (GDM_CONFIG_FILE),
+					GDM_KEY_PIDFILE);
+
+	pid = 0;
+	if (pidfile != NULL)
+		VE_IGNORE_EINTR (fp = fopen (pidfile, "r"));
+	if (fp != NULL) {
+		int r;
+		VE_IGNORE_EINTR (r = fscanf (fp, "%ld", &pid));
+		VE_IGNORE_EINTR (fclose (fp));
+		if (r != 1)
+			pid = 0;
+	}
+
+	g_free (pidfile);
+
+	errno = 0;
+	if (pid <= 1 ||
+	    (kill (pid, 0) < 0 &&
+	     errno != EPERM)) {
+		if (gui_bitching) {
+			dialog = ve_hig_dialog_new
+				(NULL /* parent */,
+				 GTK_DIALOG_MODAL /* flags */,
+				 GTK_MESSAGE_WARNING,
+				 GTK_BUTTONS_OK,
+				 FALSE /* markup */,
+				 _("GDM (The GNOME Display Manager) "
+				   "is not running."),
+				 "%s\n%s",
+				 _("You might in fact be using a different "
+				   "display manager, such as KDM "
+				   "(KDE Display Manager) or xdm."),
+				 _("If you still wish to use this feature, "
+				   "either start GDM yourself or ask your "
+				   "system administrator to start GDM."));
+
+			gtk_widget_show_all (dialog);
+			gtk_dialog_run (GTK_DIALOG (dialog));
+			gtk_widget_destroy (dialog);
+		}
+		return FALSE;
+	}
+
+	VE_IGNORE_EINTR (statret = stat (GDM_SUP_SOCKET, &s));
+	if (statret < 0 ||
+	    s.st_uid != 0 ||
+	    access (GDM_SUP_SOCKET, R_OK|W_OK) != 0) {
+		if (gui_bitching) {
+			dialog = ve_hig_dialog_new
+				(NULL /* parent */,
+				 GTK_DIALOG_MODAL /* flags */,
+				 GTK_MESSAGE_WARNING,
+				 GTK_BUTTONS_OK,
+				 FALSE /* markup */,
+				 _("Cannot communicate with GDM "
+				   "(The GNOME Display Manager)"),
+				 "%s",
+				 _("Perhaps you have an old version "
+				   "of GDM running."));
+			gtk_widget_show_all (dialog);
+			gtk_dialog_run (GTK_DIALOG (dialog));
+			gtk_widget_destroy (dialog);
+		}
+		return FALSE;
+	}
+
+	return TRUE;
+}
+#endif /* 0 */
+
+const char *
+gdmcomm_get_error_message (const char *ret,
+			   gboolean    use_xnest)
+{
+	/* These need a bit more refinement */
+	if (ret == NULL) {
+		return _("Cannot communicate with gdm, perhaps "
+			 "you have an old version running.");
+	} else if (strncmp (ret, "ERROR 0 ", strlen ("ERROR 0 ")) == 0) {
+		return _("Cannot communicate with gdm, perhaps "
+			 "you have an old version running.");
+	} else if (strncmp (ret, "ERROR 1 ", strlen ("ERROR 1 ")) == 0) {
+		return _("The allowed limit of flexible X servers reached.");
+	} else if (strncmp (ret, "ERROR 2 ", strlen ("ERROR 2 ")) == 0) {
+		return _("There were errors trying to start the X server.");
+	} else if (strncmp (ret, "ERROR 3 ", strlen ("ERROR 3 ")) == 0) {
+		return _("The X server failed.  Perhaps it is not "
+			 "configured well.");
+	} else if (strncmp (ret, "ERROR 4 ", strlen ("ERROR 4 ")) == 0) {
+		return _("Too many X sessions running.");
+	} else if (strncmp (ret, "ERROR 5 ", strlen ("ERROR 5 ")) == 0) {
+		return _("The nested X server (Xnest) cannot connect to "
+			 "your current X server.  You may be missing an "
+			 "X authorization file.");
+	} else if (strncmp (ret, "ERROR 6 ", strlen ("ERROR 6 ")) == 0) {
+		if (use_xnest)
+			return _("The nested X server (Xnest) is not "
+				 "available, or gdm is badly configured.\n"
+				 "Please install the Xnest package in "
+				 "order to use the nested login.");
+		else
+			return _("The X server is not available, "
+				 "it is likely that gdm is badly "
+				 "configured.");
+	} else if (strncmp (ret, "ERROR 7 ", strlen ("ERROR 7 ")) == 0) {
+		return _("Trying to set an unknown logout action, or trying "
+			 "to set a logout action which is not available.");
+	} else if (strncmp (ret, "ERROR 8 ", strlen ("ERROR 8 ")) == 0) {
+		return _("Virtual terminals not supported.");
+	} else if (strncmp (ret, "ERROR 9 ", strlen ("ERROR 9 ")) == 0) {
+		return _("Trying to change to an invalid virtual terminal number.");
+	} else if (strncmp (ret, "ERROR 50 ", strlen ("ERROR 50 ")) == 0) {
+		return _("Trying to update an unsupported configuration key.");
+	} else if (strncmp (ret, "ERROR 100 ", strlen ("ERROR 100 ")) == 0) {
+		return _("You do not seem to have authentication needed "
+			 "be for this operation.  Perhaps your .Xauthority "
+			 "file is not set up correctly.");
+	} else if (strncmp (ret, "ERROR 200 ", strlen ("ERROR 200 ")) == 0) {
+		return _("Too many messages were sent to gdm and it hung up"
+			 "on us.");
+	} else {
+		return _("Unknown error occurred.");
+	}
+}
Index: gnome-panel/gdmcomm.h
===================================================================
RCS file: gnome-panel/gdmcomm.h
diff -N gnome-panel/gdmcomm.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/gdmcomm.h	29 Oct 2005 07:39:55 -0000
@@ -0,0 +1,70 @@
+/*
+ *    GDMcommunication routines
+ *    (c)2001 Queen of England, (c)2002 George Lebl
+ *    
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU 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 General Public License for more details.
+ *   
+ *    You should have received a copy of the GNU General Public License
+ *    along with this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * 
+ */
+
+#ifndef __GDMCOMM_H__
+#define __GDMCOMM_H__ 1
+
+#include <gdk/gdkscreen.h>
+
+G_BEGIN_DECLS
+
+/* ***************************************************************** *
+ *  From gdm2/daemon/gdm.h                                           *
+ *  Copyright (C) 1998, 1999, 2000 Martin K. Petersen <mkp@mkp.net>  *
+ * ***************************************************************** */
+
+#define GDM_SUP_SOCKET			"/tmp/.gdm_socket"
+#define GDM_SUP_VERSION			"VERSION"			/* None */
+#define GDM_SUP_AUTH_LOCAL		"AUTH_LOCAL"			/* <xauth cookie> */
+#define GDM_SUP_FLEXI_XSERVER		"FLEXI_XSERVER"			/* <xserver type> */
+#define GDM_SUP_FLEXI_XNEST		"FLEXI_XNEST"			/* <display> <uid> <xauth cookie> <xauth file> */
+#define GDM_SUP_CONSOLE_SERVERS		"CONSOLE_SERVERS"		/* None */
+#define GDM_SUP_ALL_SERVERS		"ALL_SERVERS"			/* None */
+#define GDM_SUP_UPDATE_CONFIG		"UPDATE_CONFIG"			/* <key> */
+#define GDM_SUP_GREETERPIDS		"GREETERPIDS"			/* None */
+#define GDM_SUP_QUERY_LOGOUT_ACTION	"QUERY_LOGOUT_ACTION"		/* None */
+#define GDM_SUP_SET_LOGOUT_ACTION	"SET_LOGOUT_ACTION"		/* <action> */
+#define GDM_SUP_SET_SAFE_LOGOUT_ACTION	"SET_SAFE_LOGOUT_ACTION"	/* <action> */
+#define GDM_SUP_LOGOUT_ACTION_NONE	"NONE"				/* None */
+#define GDM_SUP_LOGOUT_ACTION_HALT	"HALT"				/* None */
+#define GDM_SUP_LOGOUT_ACTION_REBOOT	"REBOOT"			/* None */
+#define GDM_SUP_LOGOUT_ACTION_SUSPEND	"SUSPEND"			/* None */
+#define GDM_SUP_QUERY_VT       		"QUERY_VT"			/* None */
+#define GDM_SUP_SET_VT			"SET_VT"			/* <vt> */
+#define GDM_SUP_CLOSE        		"CLOSE"				/* None */
+
+
+gchar       *gdmcomm_call_gdm          (const gchar *command,
+					const gchar *auth_cookie,
+					const gchar *min_version,
+					gint         tries);
+/* This just gets a cookie of MIT-MAGIC-COOKIE-1 type */
+gchar       *gdmcomm_get_a_cookie      (GdkScreen   *screen,
+					gboolean     binary);
+/* get the gdm auth cookie */
+const gchar *gdmcomm_get_auth_cookie   (GdkScreen   *screen);
+
+gboolean     gdmcomm_check             (gboolean     gui_bitching);
+const gchar *gdmcomm_get_error_message (const gchar *ret,
+					gboolean     use_xnest);
+
+G_END_DECLS
+
+#endif /* !__GDMCOMM_H__ */
Index: gnome-panel/panel-menu-items.c
===================================================================
RCS file: /cvs/gnome/gnome-panel/gnome-panel/panel-menu-items.c,v
retrieving revision 1.12
diff -u -u -r1.12 panel-menu-items.c
--- gnome-panel/panel-menu-items.c	28 Oct 2005 15:46:25 -0000	1.12
+++ gnome-panel/panel-menu-items.c	29 Oct 2005 07:39:55 -0000
@@ -48,6 +48,7 @@
 #include "panel-lockdown.h"
 #include "panel-recent.h"
 #include "panel-stock-icons.h"
+#include "panel-switch-to.h"
 #include "panel-util.h"
 
 #define BOOKMARKS_FILENAME      ".gtk-bookmarks"
@@ -638,9 +639,9 @@
 panel_desktop_menu_item_append_menu (GtkWidget            *menu,
 				     PanelDesktopMenuItem *parent)
 {
-	gboolean   add_separator;
-	GList     *children;
-	GtkWidget *item;
+	gboolean     add_separator;
+	GList       *children;
+	GtkWidget   *item;
 
 	if (!g_object_get_data (G_OBJECT (menu), "panel-menu-needs-appending"))
 		return;
@@ -667,7 +668,7 @@
 
 	panel_menu_items_append_from_desktop (menu, "yelp.desktop", NULL);
 	panel_menu_items_append_from_desktop (menu, "gnome-about.desktop", NULL);
-
+	
 	if (parent->priv->append_lock_logout)
 		panel_menu_items_append_lock_logout (menu);
 }
@@ -1039,6 +1040,13 @@
 			children = children->next;
 		separator_inserted = GTK_IS_SEPARATOR (GTK_WIDGET (children->data));
 	}
+
+	if (!separator_inserted) {
+		add_menu_separator (menu);
+		separator_inserted = TRUE;
+	}
+
+	panel_switch_to_append_menu (menu);
 
 	if (panel_lock_screen_action_available ("lock")) {
 		item = panel_menu_items_create_action_item (PANEL_ACTION_LOCK);
Index: gnome-panel/panel-switch-to.c
===================================================================
RCS file: gnome-panel/panel-switch-to.c
diff -N gnome-panel/panel-switch-to.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/panel-switch-to.c	29 Oct 2005 07:39:55 -0000
@@ -0,0 +1,821 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * panel-switch-to.c
+ *
+ * Copyright (C) 2005 James Cape <jcape@ignore-your.tv>
+ *
+ * This 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.
+ *
+ * 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ * 	James Cape <jcape@ignore-your.tv>
+ */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "fusa-manager.h"
+
+#include "panel-stock-icons.h"
+#include "panel-globals.h"
+#include "menu.h"
+
+#include "panel-switch-to.h"
+
+enum
+{
+	USER_COLUMN,
+	N_COLUMNS
+};
+
+enum
+{
+	RESPONSE_LOGIN_SCREEN = 1
+};
+
+static void
+render_user_icon_func (GtkCellLayout   *cell_layout,
+                       GtkCellRenderer *cell,
+                       GtkTreeModel    *tree_model,
+                       GtkTreeIter     *iter,
+                       gpointer         data)
+{
+	FusaUser *user;
+	GdkPixbuf *pixbuf;
+	gboolean is_self;
+
+	user = NULL;
+	gtk_tree_model_get (tree_model, iter, USER_COLUMN, &user, -1);
+	if (user) {
+		GtkWidget *widget;
+
+		widget = GTK_WIDGET (data);
+		is_self = (fusa_user_get_uid (user) == getuid ());
+		pixbuf = fusa_user_render_icon (user, widget, 48, FALSE);
+		g_object_unref (user);
+	} else {
+		is_self = FALSE;
+		pixbuf = NULL;
+	}
+
+	g_object_set (cell, "pixbuf", pixbuf, "sensitive", !is_self, NULL);
+
+	if (pixbuf)
+		g_object_unref (pixbuf);
+}
+
+static void
+render_user_names_func (GtkCellLayout   *cell_layout,
+                        GtkCellRenderer *cell,
+                        GtkTreeModel    *tree_model,
+                        GtkTreeIter     *iter,
+                        gpointer         data)
+{
+	FusaUser *user;
+	gchar *text;
+	gboolean is_self;
+
+	user = NULL;
+	is_self = FALSE;
+	gtk_tree_model_get (tree_model, iter, USER_COLUMN, &user, -1);
+	if (user) {
+		const gchar *display_name;
+		const gchar *username;
+
+		display_name = fusa_user_get_display_name (user);
+		username = fusa_user_get_user_name (user);
+
+		is_self = (fusa_user_get_uid (user) == getuid ());
+		if (display_name && username) {
+			text = g_strconcat ("<span weight=\"bold\" size=\"larger\">", username, "</span>\n",
+					    display_name, NULL);
+		} else if (username) {
+			text = g_strconcat ("<span weight=\"bold\" size=\"larger\">", username, "</span>", NULL);
+		} else {
+			/* This shouldn't happen. */
+			text = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">%s</span>", _("Unknown User"));
+		}
+	} else {
+		text = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">%s</span>", _("Unknown User"));
+	}
+
+	g_object_set (cell, "markup", text, "sensitive", !is_self, NULL);
+	g_free (text);
+}
+
+static void
+sel_changed_cb (GtkTreeSelection *sel,
+		gpointer          user_data)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	gboolean can_switch;
+
+	can_switch = FALSE;
+
+	if (gtk_tree_selection_get_selected (sel, &model, &iter)) {
+		FusaUser *user;
+
+		gtk_tree_model_get (model, &iter, USER_COLUMN, &user, -1);
+		if (user) {
+			can_switch = (fusa_user_get_uid (user) != getuid ());
+		}
+	}
+
+	gtk_dialog_set_response_sensitive (GTK_DIALOG (user_data),
+					   GTK_RESPONSE_OK, can_switch);
+}
+
+static void
+maybe_lock_screen (GdkScreen   *screen,
+		   FusaDisplay *display)
+{
+	GConfClient *client;
+	gboolean     want_lock;
+
+	client = gconf_client_get_default ();
+	gconf_client_add_dir (client, "/apps/gnome-screensaver", 
+			      GCONF_CLIENT_PRELOAD_NONE, NULL);
+	want_lock = gconf_client_get_bool (client,
+					   "/apps/gnome-screensaver/lock",
+					   NULL);
+	gconf_client_remove_dir (client, "/apps/gnome-screensaver", NULL);
+	g_object_unref (client);
+
+	if (want_lock && !fusa_display_get_nested (display)) {
+		gchar *args[3];
+		GError *err;
+		gboolean use_gscreensaver = TRUE;
+
+		args[0] = g_find_program_in_path ("gnome-screensaver-command");
+		if (!args[0]) {
+			args[0] = g_find_program_in_path ("xscreensaver-command");
+			use_gscreensaver = FALSE;
+		}
+
+		if (!args[0])
+			return;
+
+		if (use_gscreensaver)
+			args[1] = "--lock";
+		else
+			args[1] = "-lock";
+
+		args[2] = NULL;
+
+		err = NULL;
+		if (!gdk_spawn_on_screen (screen, g_get_home_dir (), args, NULL,
+					  (G_SPAWN_STDERR_TO_DEV_NULL |
+					   G_SPAWN_STDOUT_TO_DEV_NULL),
+					  NULL, NULL, NULL, &err)) {
+			g_warning (_("Can't lock screen: %s"), err->message);
+			g_error_free (err);
+		}
+
+		if (use_gscreensaver)
+			args[1] = "--throttle";
+		else
+			args[1] = "-throttle";
+
+		if (!gdk_spawn_on_screen (screen, g_get_home_dir (), args, NULL,
+					  (G_SPAWN_STDERR_TO_DEV_NULL |
+					   G_SPAWN_STDOUT_TO_DEV_NULL),
+					  NULL, NULL, NULL, &err)) {
+			g_warning (_("Can't temporarily set screensaver to blank screen: %s"),
+				   err->message);
+			g_error_free (err);
+		}
+
+		g_free (args[0]);
+	}
+}
+
+static void
+show_error_dialog (GtkWidget    *menu_item,
+		   const GError *error)
+{
+	if (error->domain == FUSA_MANAGER_DM_ERROR) {
+		const gchar    *primary;
+		const gchar    *secondary;
+		GtkMessageType  msg_type;
+		GtkWidget      *dialog;
+
+		switch (error->code)
+		{
+			case FUSA_MANAGER_DM_ERROR_NOT_RUNNING:
+			case FUSA_MANAGER_DM_ERROR_UNKNOWN:
+				msg_type = GTK_MESSAGE_ERROR;
+				primary = _("Display Manager Unavailable");
+				secondary = _("In order for the User Selector to function properly, the GNOME Display Manager must be running, but it is not.");
+				break;
+	
+			case FUSA_MANAGER_DM_ERROR_CONSOLES_UNSUPPORTED:
+				msg_type = GTK_MESSAGE_ERROR;
+				primary = _("Multiple Consoles Not Supported");
+				secondary = _("This system does not support multiple graphical login screens.");
+				break;
+			case FUSA_MANAGER_DM_ERROR_FLEXI_LIMIT_REACHED:
+			case FUSA_MANAGER_DM_ERROR_X_LIMIT_REACHED:
+				msg_type = GTK_MESSAGE_WARNING;
+				primary = _("Too Many Sessions");
+				secondary = _("There are currently too many sessions running to create a new one. Someone must log out, or the system configuration must be changed to allow for more.");
+				break;
+			case FUSA_MANAGER_DM_ERROR_X_ERROR:
+			case FUSA_MANAGER_DM_ERROR_X_FAILED:
+			case FUSA_MANAGER_DM_ERROR_X_NOT_FOUND:
+				msg_type = GTK_MESSAGE_WARNING;
+				primary = _("Graphical System Error");
+				secondary = _("The graphical interface did not properly start, which is needed for graphical logins. It is likely that the X Window System or GDM is not properly configured.");
+				break;
+			case FUSA_MANAGER_DM_ERROR_XNEST_AUTH_ERROR:
+			case FUSA_MANAGER_DM_ERROR_PERMISSIONS:
+				msg_type = GTK_MESSAGE_WARNING;
+				primary = _("Permissions Error");
+				secondary = _("Your user does not have permissions to create additional logins.  Check your <i>~/.Xauthority</i> setup.");
+				break;
+			/* Ignore anything else. */
+			default:
+				return;
+				primary = NULL;
+				secondary = NULL;
+				msg_type = GTK_MESSAGE_WARNING;
+				break;
+		}
+
+		dialog = gtk_message_dialog_new_with_markup (NULL, 0, msg_type,
+							     GTK_BUTTONS_NONE,
+							     primary);
+		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+							  secondary);
+		g_signal_connect (dialog, "response",
+				  G_CALLBACK (gtk_widget_destroy), NULL);
+		gtk_window_present (GTK_WINDOW (dialog));
+	}
+}
+
+
+static void
+manager_new_console_cb (FusaManager  *manager,
+			FusaDisplay  *display,
+			const GError *error,
+			gpointer      data)
+{
+	if (error)
+		show_error_dialog (data, error);
+	else
+		maybe_lock_screen (data, display);
+}
+
+static void
+manager_activate_display_cb (FusaManager  *manager,
+			     FusaDisplay  *display,
+			     const GError *error,
+			     gpointer      data)
+{
+	if (error)
+		show_error_dialog (data, error);
+	else
+		maybe_lock_screen (data, display);
+}
+
+static void
+manager_new_xnest_cb (FusaManager  *manager,
+		      FusaDisplay  *display,
+		      const GError *error,
+		      gpointer      data)
+{
+	if (error)
+		show_error_dialog (data, error);
+}
+
+
+static void
+run_switch (GtkWidget   *dialog,
+	    FusaManager *manager,
+	    FusaDisplay *display,
+	    gboolean     use_xnest)
+{
+	GdkScreen *screen;
+
+	if (gtk_widget_has_screen (dialog))
+		screen = gtk_widget_get_screen (dialog);
+	else
+		screen = gdk_screen_get_default ();
+
+	if (display) {
+		fusa_manager_activate_display (manager, display, screen,
+					       manager_activate_display_cb,
+					       screen, NULL);
+		return;
+	}
+
+	if (use_xnest)
+		fusa_manager_new_xnest (manager, screen,
+					manager_new_xnest_cb, screen, NULL);
+	else
+		fusa_manager_new_console (manager, screen,
+					  manager_new_console_cb,
+					  screen, NULL);
+}
+
+typedef struct _SelectorResponseData
+{
+	GtkWidget      *dialog;
+	FusaManager    *manager;
+	GtkRadioButton *radio;
+}
+SelectorResponseData;
+
+static void
+selector_dialog_response_cb (GtkWidget *dialog,
+			     gint       response,
+			     gpointer   data)
+{
+  SelectorResponseData *res_data;
+
+  res_data = data;
+
+  if (response == GTK_RESPONSE_OK)
+    {
+      GSList *rbuttons;
+    
+      rbuttons = gtk_radio_button_get_group (res_data->radio);
+
+      while (rbuttons)
+	{
+	  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rbuttons->data)))
+	    {
+	      run_switch (res_data->dialog,
+			  g_object_get_data (G_OBJECT (res_data->dialog),
+					     "panel-fusa-manager"),
+			  g_object_get_data (G_OBJECT (rbuttons->data),
+					     "panel-fusa-radio-display"),
+			  FALSE);
+	      break;
+	    }
+
+	  rbuttons = rbuttons->next;
+	}
+
+    }
+
+  g_free (data);
+
+  gtk_widget_destroy (dialog);
+}
+
+static inline void
+show_display_selector_dialog (GtkWidget *parent,
+			      GSList    *displays)
+{
+	GtkWidget            *dialog;
+	GtkWidget            *button;
+	GtkWidget            *image;
+	GtkWidget            *hbox;
+	GtkWidget            *vbox;
+	GtkWidget            *label;
+	GtkWidget            *radio_box;
+	GtkWidget            *radio;
+	FusaUser             *user;
+	gchar                *markup;
+	SelectorResponseData *data;
+
+	dialog = gtk_dialog_new_with_buttons (_("Multiple Logins Found"),
+					      (parent ? GTK_WINDOW (parent) : NULL),
+					      GTK_DIALOG_NO_SEPARATOR, 
+				              GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+				              NULL);
+	button = gtk_dialog_add_button (GTK_DIALOG (dialog),
+					_("Continue"), GTK_RESPONSE_OK);
+	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+	image = gtk_image_new_from_stock (GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_BUTTON);
+	gtk_button_set_image (GTK_BUTTON (button), image);
+	gtk_widget_show (image);
+	/* g_signal_connect (dialog, "style-set", G_CALLBACK (dialog_hig_fix),
+			  GUINT_TO_POINTER (!NULL)); */
+
+	hbox = gtk_hbox_new (FALSE, 12);
+	gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
+			    hbox, FALSE, FALSE, 0);
+	gtk_widget_show (hbox);
+  
+	image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_QUESTION,
+				          GTK_ICON_SIZE_DIALOG);
+	gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
+	gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+	gtk_widget_show (image);
+
+	vbox = gtk_vbox_new (FALSE, 12);
+	gtk_container_add (GTK_CONTAINER (hbox), vbox);
+	gtk_widget_show (vbox);
+
+	markup = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s",
+			          _("Multiple Logins Found"), 
+			          _("The user you want to switch to is logged in multiple times on this computer. Which login do you want to switch to?"));
+	label = gtk_label_new (markup);
+	g_free (markup);
+	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+	gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+	gtk_container_add (GTK_CONTAINER (vbox), label);
+	gtk_widget_show (label);
+
+	radio_box = gtk_vbox_new (FALSE, 6);
+	gtk_container_add (GTK_CONTAINER (vbox), radio_box);
+	gtk_widget_show (radio_box);
+
+	user = fusa_display_get_user (displays->data);
+	radio = NULL;
+	while (displays) {
+		if (fusa_display_get_nested (displays->data))
+			markup = g_strdup_printf (_("%s in a nested window on console %d"),
+						  fusa_user_get_display_name (user),
+						  fusa_display_get_console (displays->data));
+		else
+			markup = g_strdup_printf (_("%s on console %d"),
+						  fusa_user_get_display_name (user),
+						  fusa_display_get_console (displays->data));
+		radio = gtk_radio_button_new_with_label_from_widget (radio ? GTK_RADIO_BUTTON (radio) : NULL,
+								     markup);
+		g_object_set_data (G_OBJECT (radio), "panel-fusa-radio-display",
+				   displays->data); 
+		g_free (markup);
+		gtk_box_pack_start (GTK_BOX (radio_box), radio, FALSE, FALSE, 0);
+		gtk_widget_show (radio);
+
+		displays = displays->next;
+	}
+
+	data = g_new0 (SelectorResponseData, 1);
+	data->dialog = parent;
+	data->radio = GTK_RADIO_BUTTON (radio);
+	g_signal_connect (dialog, "response",
+			  G_CALLBACK (selector_dialog_response_cb), data);
+
+	gtk_window_present (GTK_WINDOW (dialog));
+	gtk_widget_set_sensitive (GTK_WIDGET (parent), FALSE);
+}
+
+static void
+dialog_response_cb (GtkDialog *dialog,
+		    gint       response,
+		    gpointer   user_data)
+{
+	FusaManager  *manager;
+	GSList       *displays;
+	FusaDisplay  *target;
+	GtkTreeModel *model;
+	GtkTreeIter   iter;
+	FusaUser     *user;
+
+	switch (response)
+	{
+	case RESPONSE_LOGIN_SCREEN:
+		manager = g_object_get_data (G_OBJECT (dialog),
+					     "panel-fusa-manager");
+		target = NULL;
+		displays = fusa_manager_list_displays (manager);
+		while (displays) {
+			if (!fusa_display_get_user (displays->data) &&
+			    !fusa_display_get_nested (displays->data)) {
+				target = displays->data;
+				break;
+			}
+
+			displays = g_slist_delete_link (displays, displays);
+		}
+
+		g_slist_free (displays);
+
+		run_switch (GTK_WIDGET (dialog), manager, target, FALSE);
+		break;
+
+	case GTK_RESPONSE_CANCEL:
+		break;
+
+	case GTK_RESPONSE_OK:
+		if (!gtk_tree_selection_get_selected (user_data, &model,
+						      &iter)) {
+			/* Shouldn't happen */
+			gtk_dialog_set_response_sensitive (dialog,
+							   GTK_RESPONSE_OK,
+							   FALSE);
+			return;
+		}
+
+		manager = g_object_get_data (G_OBJECT (dialog),
+					     "panel-fusa-manager");
+
+		user = NULL;
+		gtk_tree_model_get (model, &iter, USER_COLUMN, &user, -1);
+		if (!user) { /* Also shouldn't happen */
+			gtk_dialog_set_response_sensitive (dialog,
+							   GTK_RESPONSE_OK,
+							   FALSE);
+			return;
+		}
+
+		displays = fusa_user_get_displays (user);
+		if (displays) {
+			if (g_slist_length (displays) > 1) {
+				GSList *li, *unique_consoles, *unique_displays;
+				FusaDisplay *this_display;
+				gint this_console;
+  
+				this_display = fusa_manager_get_display_by_screen (manager,
+										   gtk_widget_get_screen (user_data));
+				this_console = fusa_display_get_console (this_display);
+
+				/* Only show unique consoles for this user */
+				unique_consoles = NULL;
+				unique_displays = NULL;
+				li = displays;
+				while (li) {
+					GSList *li2;
+					gint console;
+
+					console = fusa_display_get_console (li->data);
+					if (console == this_console && li->data != this_display)
+						li2 = NULL;
+					else
+						li2 = g_slist_find (unique_consoles,
+								    GINT_TO_POINTER (console));
+
+					if (!li2) {
+						unique_consoles = g_slist_prepend (unique_consoles,
+										   GINT_TO_POINTER (console));
+						unique_displays = g_slist_prepend (unique_displays,
+										   li->data);
+					}
+
+					li = li->next;
+				}
+
+				g_slist_free (unique_consoles);
+				if (g_slist_length (unique_displays) > 1)
+					show_display_selector_dialog (GTK_WIDGET (dialog),
+								      unique_displays);
+				else
+					run_switch (GTK_WIDGET (dialog),
+						    manager,
+						    unique_displays->data,
+						    FALSE);
+
+				g_slist_free (unique_displays);
+			} else {
+				run_switch (GTK_WIDGET (dialog), manager,
+					    displays->data, FALSE);
+			}
+
+			g_slist_free (displays);
+		} else {
+			displays = fusa_manager_list_displays (manager);
+
+			if (g_slist_length (displays) == 1) {
+				/* Only one display open (this one), so create a new one */
+				run_switch (GTK_WIDGET (dialog), manager, NULL,
+					    FALSE);
+				g_slist_free (displays);
+			} else {
+				/* Iterate through to find an open display */
+				while (displays) {
+					/* If the display is user-less, and either we want nested and it
+					 * is, or we don't, and it isn't, switch to it, free the list,
+					 * and leave. */
+					if (!fusa_display_get_user (displays->data) &&
+					    !fusa_display_get_nested (displays->data)) {
+						run_switch (GTK_WIDGET (dialog),
+							    manager,
+							    displays->data,
+							    FALSE);
+						/* The pointer will be invalid but !NULL after this */
+						g_slist_free (displays);
+						break;
+					}
+	    
+					displays = g_slist_delete_link (displays,
+									displays);
+				}
+
+				if (!displays)
+					run_switch (GTK_WIDGET (dialog),
+						    manager, NULL, FALSE);
+			}
+		}
+		g_object_unref (user);
+		break;
+	}
+
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+manager_user_added_cb (FusaManager *manager,
+		       FusaUser    *user,
+		       gpointer     data)
+{
+	GtkListStore *store;
+	GtkTreeIter   iter;
+
+	store = data;
+
+	gtk_list_store_append (store, &iter);
+	gtk_list_store_set (store, &iter, USER_COLUMN, user, -1);
+}
+
+static void
+manager_user_removed_cb (FusaManager *manager,
+			 FusaUser    *user,
+			 gpointer     data)
+{
+	GtkTreeModel *model;
+	GtkTreeIter   iter;
+
+	model = data;
+
+	if (gtk_tree_model_get_iter_first (model, &iter)) {
+		uid_t user_id;
+		FusaUser *tmp;
+
+		user_id = fusa_user_get_uid (user);
+
+		do {
+			tmp = NULL;
+			gtk_tree_model_get (model, &iter, USER_COLUMN, &tmp, -1);
+			if (tmp) {
+				if (fusa_user_get_uid (tmp) == user_id) {
+					gtk_list_store_remove (GTK_LIST_STORE (model),
+							       &iter);
+				}
+				g_object_unref (tmp);
+			}
+		} while (gtk_tree_model_iter_next (model, &iter));
+	}
+}
+
+static void
+switch_to_activate_cb (GtkWidget *menu_item)
+{
+	static gpointer    dialog = NULL;
+	GtkWidget         *button;
+	GtkWidget         *image;
+	GtkWidget         *scrwin;
+	GtkWidget         *tree_view;
+	GtkListStore      *store;
+	FusaManager       *manager;
+	GSList            *users;
+	GtkTreeViewColumn *col;
+	GtkCellRenderer   *cell;
+	GtkTreeSelection  *sel;
+
+	if (dialog) {
+		gtk_window_present (GTK_WINDOW (dialog));
+		return;
+	}
+
+	dialog = gtk_dialog_new_with_buttons (_("Switch To A Different User"),
+					      NULL, GTK_DIALOG_NO_SEPARATOR,
+					      NULL);
+	g_object_set_data (G_OBJECT (dialog),
+			   "panel-fusa-menu-item", menu_item);
+	g_object_add_weak_pointer (G_OBJECT (dialog), (gpointer *) &dialog);
+
+	/* Buttons */
+	button = gtk_dialog_add_button (GTK_DIALOG (dialog),
+					_("_Login Screen"),
+					RESPONSE_LOGIN_SCREEN);
+	image = gtk_image_new_from_icon_name ("gdm", GTK_ICON_SIZE_BUTTON);
+	gtk_button_set_image (GTK_BUTTON (button), image);
+	gtk_widget_show (image);
+
+	gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL,
+			       GTK_RESPONSE_CANCEL);
+
+	button = gtk_dialog_add_button (GTK_DIALOG (dialog),
+					_("_Switch"),
+					GTK_RESPONSE_OK);
+	image = gtk_image_new_from_icon_name ("stock_people",
+					      GTK_ICON_SIZE_BUTTON);
+	gtk_button_set_image (GTK_BUTTON (button), image);
+	gtk_widget_show (image);
+
+	/* Dialog Configuratin' */
+	gtk_container_set_border_width (GTK_CONTAINER (dialog), 7);
+	gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 14);
+
+	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+	scrwin = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrwin),
+					GTK_POLICY_NEVER,
+					GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrwin),
+					     GTK_SHADOW_IN);
+	gtk_container_set_border_width (GTK_CONTAINER (scrwin), 5);
+	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), scrwin);
+	gtk_widget_set_size_request (scrwin, 440, 200);
+	gtk_widget_show (scrwin);
+
+	store = gtk_list_store_new (N_COLUMNS, FUSA_TYPE_USER);
+	g_object_set_data_full (G_OBJECT (dialog),
+				"panel-fusa-store", store, g_object_unref);
+
+	manager = fusa_manager_ref_default ();
+	g_object_set_data_full (G_OBJECT (dialog),
+				"panel-fusa-manager", manager, g_object_unref);
+	g_signal_connect (manager, "user-added",
+			  G_CALLBACK (manager_user_added_cb), store);
+	g_signal_connect (manager, "user-removed",
+			  G_CALLBACK (manager_user_removed_cb), store);
+	for (users = fusa_manager_list_users (manager);
+	     users != NULL;
+	     users = g_slist_delete_link (users, users)) {
+		GtkTreeIter iter;
+
+		gtk_list_store_append (store, &iter);
+		gtk_list_store_set (store, &iter,
+				    USER_COLUMN, users->data,
+				    -1);
+	}
+
+	/* 
+	icon_view = gtk_icon_view_new_with_model (GTK_TREE_MODEL (store));
+	gtk_icon_view_set_orientation (GTK_ICON_VIEW (icon_view),
+				       GTK_ORIENTATION_HORIZONTAL);
+	gtk_container_add (GTK_CONTAINER (scrwin), icon_view);
+	gtk_widget_show (icon_view);
+
+	cell = gtk_cell_renderer_pixbuf_new ();
+	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (icon_view), cell, FALSE);
+	gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (icon_view), cell,
+					    render_user_icon_func,
+					    icon_view, NULL);
+
+	cell = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (icon_view), cell, TRUE);
+	gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (icon_view), cell,
+					    render_user_names_func,
+					    icon_view, NULL);
+	*/
+
+	tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree_view), FALSE);
+	gtk_container_add (GTK_CONTAINER (scrwin), tree_view);
+	gtk_widget_show (tree_view);
+	
+	col = gtk_tree_view_column_new ();
+	gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), col);
+
+	cell = gtk_cell_renderer_pixbuf_new ();
+	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (col), cell, FALSE);
+	gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (col), cell,
+					    render_user_icon_func,
+					    tree_view, NULL);
+
+	cell = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (col), cell, TRUE);
+	gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (col), cell,
+					    render_user_names_func,
+					    tree_view, NULL);
+
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+	g_signal_connect (sel, "changed", G_CALLBACK (sel_changed_cb), dialog);
+
+	g_signal_connect (dialog, "response",
+			  G_CALLBACK (dialog_response_cb), sel);
+
+	gtk_window_present (GTK_WINDOW (dialog));
+}
+
+void
+panel_switch_to_append_menu (GtkWidget *top_menu)
+{
+	GtkWidget *menu_item;
+
+	menu_item = gtk_image_menu_item_new ();
+	setup_menu_item_with_icon (menu_item,
+				   panel_menu_icon_get_size (),
+				   "stock_people",
+				   NULL,
+				   _("Switch To..."),
+				   FALSE);
+	g_signal_connect (menu_item, "activate",
+			  G_CALLBACK (switch_to_activate_cb), NULL);
+	gtk_menu_shell_append (GTK_MENU_SHELL (top_menu), menu_item);
+	gtk_widget_show_all (menu_item);
+}
Index: gnome-panel/panel-switch-to.h
===================================================================
RCS file: gnome-panel/panel-switch-to.h
diff -N gnome-panel/panel-switch-to.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnome-panel/panel-switch-to.h	29 Oct 2005 07:39:55 -0000
@@ -0,0 +1,36 @@
+/*
+ * panel-switch-to.h
+ *
+ * Copyright (C) 2005 James Cape <jcape@ignore-your.tv>
+ *
+ * This 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.
+ *
+ * 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ * 	James Cape <jcape@ignore-your.tv>
+ */
+
+#ifndef __PANEL_SWITCH_TO_H__
+#define __PANEL_SWITCH_TO_H__
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+void panel_switch_to_append_menu (GtkWidget *menu);
+
+G_END_DECLS
+
+#endif /* !__PANEL_SWITCH_TO_H__ */
