gnome-shell r183 - in trunk: . js/ui src src/gdmuser



Author: walters
Date: Wed Feb  4 18:45:38 2009
New Revision: 183
URL: http://svn.gnome.org/viewvc/gnome-shell?rev=183&view=rev

Log:
Merge branch 'statusmenu'

Added:
   trunk/src/gdmuser/
   trunk/src/gdmuser/gdm-user-chooser-dialog.c
   trunk/src/gdmuser/gdm-user-chooser-dialog.h
   trunk/src/gdmuser/gdm-user-chooser-widget.c
   trunk/src/gdmuser/gdm-user-chooser-widget.h
   trunk/src/gdmuser/gdm-user-manager.c
   trunk/src/gdmuser/gdm-user-manager.h
   trunk/src/gdmuser/gdm-user-private.h
   trunk/src/gdmuser/gdm-user.c
   trunk/src/gdmuser/gdm-user.h
   trunk/src/shell-status-menu.c
   trunk/src/shell-status-menu.h
Modified:
   trunk/configure.ac
   trunk/js/ui/button.js
   trunk/js/ui/panel.js
   trunk/src/Makefile.am

Modified: trunk/configure.ac
==============================================================================
--- trunk/configure.ac	(original)
+++ trunk/configure.ac	Wed Feb  4 18:45:38 2009
@@ -21,6 +21,7 @@
 PKG_CHECK_MODULES(MUTTER_PLUGIN, gtk+-2.0 dbus-glib-1 metacity-plugins gjs-gi-1.0)
 PKG_CHECK_MODULES(TIDY, clutter-0.8)
 PKG_CHECK_MODULES(BIG, clutter-cairo-0.8 gtk+-2.0 librsvg-2.0)
+PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-2.0)
 PKG_CHECK_MODULES(TRAY, gtk+-2.0)
 PKG_CHECK_MODULES(TASKPANEL, libwnck-1.0 dbus-glib-1)
 # We require libgnomeui for generating thumbnails for recent files with GnomeThumbnailFactory.

Modified: trunk/js/ui/button.js
==============================================================================
--- trunk/js/ui/button.js	(original)
+++ trunk/js/ui/button.js	Wed Feb  4 18:45:38 2009
@@ -1,29 +1,21 @@
 /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
 
 const Clutter = imports.gi.Clutter;
+const Big = imports.gi.Big;
 const Tweener = imports.tweener.tweener;
 
 const DEFAULT_BUTTON_COLOR = new Clutter.Color();
-DEFAULT_BUTTON_COLOR.from_pixel(0xeeddccff);
+DEFAULT_BUTTON_COLOR.from_pixel(0xeeddcc66);
 
 const DEFAULT_PRESSED_BUTTON_COLOR = new Clutter.Color();
-DEFAULT_PRESSED_BUTTON_COLOR.from_pixel(0xccbbaaff);
+DEFAULT_PRESSED_BUTTON_COLOR.from_pixel(0xccbbaa66);
 
-// Time for animation making the button darker
-const ANIMATION_TIME = 0.3;
-
-const NO_OPACITY = 0;
-
-const PARTIAL_OPACITY = 0.4 * 255;
-
-const FULL_OPACITY = 255;
-
-function Button(text, buttonColor, pressedButtonColor, staysPressed, minWidth, minHeight) {
-    this._init(text, buttonColor, pressedButtonColor, staysPressed, minWidth, minHeight);
+function Button(widget, buttonColor, pressedButtonColor, staysPressed, minWidth, minHeight) {
+    this._init(widget, buttonColor, pressedButtonColor, staysPressed, minWidth, minHeight);
 }
 
 Button.prototype = {
-    _init : function(text, buttonColor, pressedButtonColor, staysPressed, minWidth, minHeight) {
+    _init : function(widgetOrText, buttonColor, pressedButtonColor, staysPressed, minWidth, minHeight) {
         let me = this;
 
         this._buttonColor = buttonColor
@@ -47,29 +39,29 @@
         this._isBetweenPressAndRelease = false;
         this._mouseIsOverButton = false;
 
-        this.button = new Clutter.Group({reactive: true});
-        this._background = new Clutter.Rectangle({ color: this._buttonColor});
-        this._pressedBackground = new Clutter.Rectangle({ color: this._pressedButtonColor, opacity: NO_OPACITY});
-        this._label = new Clutter.Label({ font_name: "Sans Bold 16px",
-                                         text: text});
-        this._label.set_position(5, 5);
-        let backgroundWidth = Math.max(this._label.get_width()+10, minWidth);
-        let backgroundHeight = Math.max(this._label.get_height()+10, minHeight);
-        this._background.set_width(backgroundWidth)
-        this._background.set_height(backgroundHeight)
-        this._pressedBackground.set_width(backgroundWidth)
-        this._pressedBackground.set_height(backgroundHeight)
-        this.button.add_actor(this._background);
-        this.button.add_actor(this._pressedBackground);
-        this.button.add_actor(this._label);
+        this.button = new Big.Box({ reactive: true,
+                                    corner_radius: 5,
+                                    padding_left: 4,
+                                    padding_right: 4,
+                                    orientation: Big.BoxOrientation.HORIZONTAL,
+                                    y_align: Big.BoxAlignment.CENTER
+                                  });
+        if (typeof widgetOrText == 'string') {
+            this._widget = new Clutter.Label({ font_name: "Sans Bold 16px",
+                                               text: widgetOrText });
+        } else {
+            this._widget = widgetOrText;
+        }
+
+        this.button.append(this._widget, Big.BoxPackFlags.EXPAND);
+
+        this._minWidth = minWidth;
+        this._minHeight = minHeight;
+
         this.button.connect('button-press-event',
             function(o, event) {
                 me._isBetweenPressAndRelease = true;
-                Tweener.addTween(me._pressedBackground,
-                                { time: ANIMATION_TIME,
-                                  opacity: FULL_OPACITY,
-                                  transition: "linear"
-                                });
+                me.button.backgroundColor = me._pressedButtonColor;
                 return false;
             });
         this.button.connect('button-release-event',
@@ -86,8 +78,7 @@
             function(o, event) {
                 me._mouseIsOverButton = true;
                 if (!me._active) {
-                    Tweener.removeTweens(me._pressedBackground);
-                    me._pressedBackground.set_opacity(PARTIAL_OPACITY);
+                    me.button.backgroundColor = me._buttonColor;
                 }
                 return false;
             });
@@ -96,8 +87,7 @@
                 me._isBetweenPressAndRelease = false;
                 me._mouseIsOverButton = false;
                 if (!me._active) {
-                    Tweener.removeTweens(me._pressedBackground);
-                    me._pressedBackground.set_opacity(NO_OPACITY);
+                    me.button.backgroundColor = null;
                 }
                 return false;
             });
@@ -106,11 +96,10 @@
     release : function() {
         if (!this._isBetweenPressAndRelease) {
             this._active = false;
-            Tweener.removeTweens(this._pressedBackground);
             if (this._mouseIsOverButton) {
-                this._pressedBackground.set_opacity(PARTIAL_OPACITY);
+                this.button.backgroundColor = this._buttonColor;
             } else {
-                this._pressedBackground.set_opacity(NO_OPACITY);
+                this.button.backgroundColor = null;
             }
         }
     }

Modified: trunk/js/ui/panel.js
==============================================================================
--- trunk/js/ui/panel.js	(original)
+++ trunk/js/ui/panel.js	Wed Feb  4 18:45:38 2009
@@ -13,6 +13,8 @@
 const TRAY_HEIGHT = 24;
 const PANEL_BACKGROUND_COLOR = new Clutter.Color();
 PANEL_BACKGROUND_COLOR.from_pixel(0xeeddccff);
+const PANEL_BUTTON_COLOR = new Clutter.Color();
+PANEL_BUTTON_COLOR.from_pixel(0xccbbaa66);
 const PANEL_BORDER_COLOR = new Clutter.Color();
 PANEL_BORDER_COLOR.from_pixel(0x000000ff);
 const PRESSED_BUTTON_BACKGROUND_COLOR = new Clutter.Color();
@@ -37,10 +39,25 @@
                                   border_bottom: 1,
                                   border_color: PANEL_BORDER_COLOR });
 
-        this.button = new Button.Button("Activities", PANEL_BACKGROUND_COLOR, PRESSED_BUTTON_BACKGROUND_COLOR, true, null, PANEL_HEIGHT);
+        this.button = new Button.Button("Activities", PANEL_BUTTON_COLOR, PRESSED_BUTTON_BACKGROUND_COLOR, true, null, PANEL_HEIGHT);
 
         this._box.append(this.button.button, Big.BoxPackFlags.NONE);
 
+        let statusbox = new Big.Box();
+        this._statusmenu = new Shell.StatusMenu();
+        statusbox.append(this._statusmenu, Big.BoxPackFlags.NONE);
+        let statusbutton = new Button.Button(statusbox, PANEL_BUTTON_COLOR, PRESSED_BUTTON_BACKGROUND_COLOR,
+                                             true, null, PANEL_HEIGHT);
+        statusbutton.button.connect('button-press-event', function (b, e) {
+            me._statusmenu.toggle(e);
+            return false;
+        });
+        this._box.append(statusbutton.button, Big.BoxPackFlags.END);
+        // We get a deactivated event when the popup disappears
+        this._statusmenu.connect('deactivated', function (sm) {
+            statusbutton.release();
+        });
+
         this._clock = new Clutter.Label({ font_name: "Sans Bold 16px",
                                           text: "" });
         let pad = (PANEL_HEIGHT - this._clock.height) / 2;

Modified: trunk/src/Makefile.am
==============================================================================
--- trunk/src/Makefile.am	(original)
+++ trunk/src/Makefile.am	Wed Feb  4 18:45:38 2009
@@ -7,6 +7,7 @@
 
 include Makefile-tidy.am
 include Makefile-big.am
+include Makefile-gdmuser.am
 include Makefile-tray.am
 include Makefile-taskpanel.am
 
@@ -42,6 +43,8 @@
 	shell-process.h				\
 	shell-global.c				\
 	shell-global.h				\
+	shell-status-menu.c				\
+	shell-status-menu.h				\
 	shell-tray-manager.c			\
 	shell-tray-manager.h			\
 	shell-wm.c				\
@@ -77,6 +80,7 @@
 	$(MUTTER_PLUGIN_LIBS)	\
         $(LIBGNOMEUI_LIBS)      \
 	libbig-1.0.la		\
+	libgdmuser-1.0.la	\
 	libtidy-1.0.la		\
 	libtray.la
 libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags)
@@ -84,13 +88,15 @@
 typelibdir = $(pkglibdir)
 typelib_DATA = Shell-0.1.typelib Tidy-1.0.typelib Big-1.0.typelib
 
-Shell-0.1.gir: $(metacity) $(G_IR_SCANNER) libgnome-shell.la Makefile
+Shell-0.1.gir: $(metacity) $(G_IR_SCANNER) Big-1.0.gir libgnome-shell.la Makefile
 	$(G_IR_SCANNER)		\
 		--namespace=Shell			\
 		--nsversion=0.1				\
 		--add-include-path=$(libdir)/metacity/ \
 		--include=Clutter-0.8			\
 		--include=Meta-2.25			\
+		--add-include-path=$(builddir)     \
+		--include=Big-1.0     \
 		--program=metacity			\
 	        --program-arg=--mutter-plugins=$$(pwd)/libgnome-shell.la \
 		$(libgnome_shell_la_gir_sources)	\
@@ -101,7 +107,7 @@
 # The dependency on libgnome-shell.la here is because g-ir-compiler opens it
 # (not the fake library, since we've already done the rewriting)
 Shell-0.1.typelib: libgnome-shell.la Shell-0.1.gir
-	LD_LIBRARY_PATH=$${LD_LIBRARY_PATH:+$$LD_LIBRARY_PATH:}. g-ir-compiler --includedir=$(libdir)/metacity/ Shell-0.1.gir -o $@
+	LD_LIBRARY_PATH=$${LD_LIBRARY_PATH:+$$LD_LIBRARY_PATH:}. g-ir-compiler --includedir=$(builddir) --includedir=$(libdir)/metacity/ Shell-0.1.gir -o $@
 CLEANFILES += Shell-1.0.typelib
 
 Tidy-1.0.gir: $(metacity) $(G_IR_SCANNER) libgnome-shell.la libtidy-1.0.la Makefile

Added: trunk/src/gdmuser/gdm-user-chooser-dialog.c
==============================================================================
--- (empty file)
+++ trunk/src/gdmuser/gdm-user-chooser-dialog.c	Wed Feb  4 18:45:38 2009
@@ -0,0 +1,208 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
+ *
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "gdm-user-chooser-widget.h"
+#include "gdm-user-chooser-dialog.h"
+
+#define GDM_USER_CHOOSER_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_USER_CHOOSER_DIALOG, GdmUserChooserDialogPrivate))
+
+struct GdmUserChooserDialogPrivate
+{
+        GtkWidget *chooser_widget;
+};
+
+enum {
+        PROP_0,
+};
+
+static void     gdm_user_chooser_dialog_class_init  (GdmUserChooserDialogClass *klass);
+static void     gdm_user_chooser_dialog_init        (GdmUserChooserDialog      *user_chooser_dialog);
+static void     gdm_user_chooser_dialog_finalize    (GObject                       *object);
+
+G_DEFINE_TYPE (GdmUserChooserDialog, gdm_user_chooser_dialog, GTK_TYPE_DIALOG)
+
+char *
+gdm_user_chooser_dialog_get_chosen_user_name (GdmUserChooserDialog *dialog)
+{
+        char *user_name;
+
+        g_return_val_if_fail (GDM_IS_USER_CHOOSER_DIALOG (dialog), NULL);
+
+        user_name = gdm_user_chooser_widget_get_chosen_user_name (GDM_USER_CHOOSER_WIDGET (dialog->priv->chooser_widget));
+
+        return user_name;
+}
+
+void
+gdm_user_chooser_dialog_set_show_user_other (GdmUserChooserDialog *dialog,
+                                             gboolean              show_user)
+{
+        g_return_if_fail (GDM_IS_USER_CHOOSER_DIALOG (dialog));
+
+        gdm_user_chooser_widget_set_show_user_other (GDM_USER_CHOOSER_WIDGET (dialog->priv->chooser_widget), show_user);
+}
+
+void
+gdm_user_chooser_dialog_set_show_user_guest (GdmUserChooserDialog *dialog,
+                                             gboolean              show_user)
+{
+        g_return_if_fail (GDM_IS_USER_CHOOSER_DIALOG (dialog));
+
+        gdm_user_chooser_widget_set_show_user_guest (GDM_USER_CHOOSER_WIDGET (dialog->priv->chooser_widget), show_user);
+}
+
+void
+gdm_user_chooser_dialog_set_show_user_auto (GdmUserChooserDialog *dialog,
+                                            gboolean              show_user)
+{
+        g_return_if_fail (GDM_IS_USER_CHOOSER_DIALOG (dialog));
+
+        gdm_user_chooser_widget_set_show_user_auto (GDM_USER_CHOOSER_WIDGET (dialog->priv->chooser_widget), show_user);
+}
+
+static void
+gdm_user_chooser_dialog_set_property (GObject        *object,
+                                      guint           prop_id,
+                                      const GValue   *value,
+                                      GParamSpec     *pspec)
+{
+        switch (prop_id) {
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gdm_user_chooser_dialog_get_property (GObject        *object,
+                                      guint           prop_id,
+                                      GValue         *value,
+                                      GParamSpec     *pspec)
+{
+        switch (prop_id) {
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static GObject *
+gdm_user_chooser_dialog_constructor (GType                  type,
+                                     guint                  n_construct_properties,
+                                     GObjectConstructParam *construct_properties)
+{
+        GdmUserChooserDialog      *user_chooser_dialog;
+
+        user_chooser_dialog = GDM_USER_CHOOSER_DIALOG (G_OBJECT_CLASS (gdm_user_chooser_dialog_parent_class)->constructor (type,
+                                                                                                                           n_construct_properties,
+                                                                                                                           construct_properties));
+
+        return G_OBJECT (user_chooser_dialog);
+}
+
+static void
+gdm_user_chooser_dialog_dispose (GObject *object)
+{
+        G_OBJECT_CLASS (gdm_user_chooser_dialog_parent_class)->dispose (object);
+}
+
+static void
+gdm_user_chooser_dialog_class_init (GdmUserChooserDialogClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->get_property = gdm_user_chooser_dialog_get_property;
+        object_class->set_property = gdm_user_chooser_dialog_set_property;
+        object_class->constructor = gdm_user_chooser_dialog_constructor;
+        object_class->dispose = gdm_user_chooser_dialog_dispose;
+        object_class->finalize = gdm_user_chooser_dialog_finalize;
+
+        g_type_class_add_private (klass, sizeof (GdmUserChooserDialogPrivate));
+}
+
+static void
+on_response (GdmUserChooserDialog *dialog,
+             gint                      response_id)
+{
+        switch (response_id) {
+        default:
+                break;
+        }
+}
+
+static void
+gdm_user_chooser_dialog_init (GdmUserChooserDialog *dialog)
+{
+
+        dialog->priv = GDM_USER_CHOOSER_DIALOG_GET_PRIVATE (dialog);
+
+        dialog->priv->chooser_widget = gdm_user_chooser_widget_new ();
+
+        gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), dialog->priv->chooser_widget);
+
+        gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+                                GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                                GTK_STOCK_OK, GTK_RESPONSE_OK,
+                                NULL);
+        g_signal_connect (dialog,
+                          "response",
+                          G_CALLBACK (on_response),
+                          dialog);
+
+        gtk_widget_show_all (GTK_WIDGET (dialog));
+}
+
+static void
+gdm_user_chooser_dialog_finalize (GObject *object)
+{
+        GdmUserChooserDialog *user_chooser_dialog;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_USER_CHOOSER_DIALOG (object));
+
+        user_chooser_dialog = GDM_USER_CHOOSER_DIALOG (object);
+
+        g_return_if_fail (user_chooser_dialog->priv != NULL);
+
+        G_OBJECT_CLASS (gdm_user_chooser_dialog_parent_class)->finalize (object);
+}
+
+GtkWidget *
+gdm_user_chooser_dialog_new (void)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_USER_CHOOSER_DIALOG,
+                               NULL);
+
+        return GTK_WIDGET (object);
+}

Added: trunk/src/gdmuser/gdm-user-chooser-dialog.h
==============================================================================
--- (empty file)
+++ trunk/src/gdmuser/gdm-user-chooser-dialog.h	Wed Feb  4 18:45:38 2009
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
+ *
+ * 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 __GDM_USER_CHOOSER_DIALOG_H
+#define __GDM_USER_CHOOSER_DIALOG_H
+
+#include <glib-object.h>
+#include <gtk/gtkdialog.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_USER_CHOOSER_DIALOG         (gdm_user_chooser_dialog_get_type ())
+#define GDM_USER_CHOOSER_DIALOG(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_USER_CHOOSER_DIALOG, GdmUserChooserDialog))
+#define GDM_USER_CHOOSER_DIALOG_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_USER_CHOOSER_DIALOG, GdmUserChooserDialogClass))
+#define GDM_IS_USER_CHOOSER_DIALOG(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_USER_CHOOSER_DIALOG))
+#define GDM_IS_USER_CHOOSER_DIALOG_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_USER_CHOOSER_DIALOG))
+#define GDM_USER_CHOOSER_DIALOG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_USER_CHOOSER_DIALOG, GdmUserChooserDialogClass))
+
+typedef struct GdmUserChooserDialogPrivate GdmUserChooserDialogPrivate;
+
+typedef struct
+{
+        GtkDialog                    parent;
+        GdmUserChooserDialogPrivate *priv;
+} GdmUserChooserDialog;
+
+typedef struct
+{
+        GtkDialogClass   parent_class;
+} GdmUserChooserDialogClass;
+
+GType                  gdm_user_chooser_dialog_get_type                   (void);
+
+GtkWidget            * gdm_user_chooser_dialog_new                        (void);
+
+char *                 gdm_user_chooser_dialog_get_chosen_user_name       (GdmUserChooserDialog *dialog);
+void                   gdm_user_chooser_dialog_set_show_other_user        (GdmUserChooserDialog *dialog,
+                                                                           gboolean              show);
+void                   gdm_user_chooser_dialog_set_show_user_guest        (GdmUserChooserDialog *dialog,
+                                                                           gboolean              show);
+void                   gdm_user_chooser_dialog_set_show_user_auto         (GdmUserChooserDialog *dialog,
+                                                                           gboolean              show);
+G_END_DECLS
+
+#endif /* __GDM_USER_CHOOSER_DIALOG_H */

Added: trunk/src/gdmuser/gdm-user-chooser-widget.c
==============================================================================
--- (empty file)
+++ trunk/src/gdmuser/gdm-user-chooser-widget.c	Wed Feb  4 18:45:38 2009
@@ -0,0 +1,723 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
+ * Copyright (C) 2007 Ray Strode <rstrode redhat com>
+ *
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include <gconf/gconf-client.h>
+
+#include "gdm-user-manager.h"
+#include "gdm-user-chooser-widget.h"
+
+
+#define KEY_DISABLE_USER_LIST "/apps/gdm/simple-greeter/disable_user_list"
+
+enum {
+        USER_NO_DISPLAY              = 1 << 0,
+        USER_ACCOUNT_DISABLED        = 1 << 1,
+};
+
+#define DEFAULT_USER_ICON "stock_person"
+
+#define GDM_USER_CHOOSER_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_USER_CHOOSER_WIDGET, GdmUserChooserWidgetPrivate))
+
+#define MAX_ICON_SIZE 128
+
+struct GdmUserChooserWidgetPrivate
+{
+        GdmUserManager *manager;
+        GtkIconTheme   *icon_theme;
+
+        GdkPixbuf      *logged_in_pixbuf;
+        GdkPixbuf      *stock_person_pixbuf;
+
+        guint           loaded : 1;
+        guint           show_user_other : 1;
+        guint           show_user_guest : 1;
+        guint           show_user_auto : 1;
+        guint           show_normal_users : 1;
+
+        guint           load_idle_id;
+};
+
+enum {
+        PROP_0,
+        PROP_SHOW_USER_GUEST,
+        PROP_SHOW_USER_AUTO,
+        PROP_SHOW_USER_OTHER,
+};
+
+static void     gdm_user_chooser_widget_class_init  (GdmUserChooserWidgetClass *klass);
+static void     gdm_user_chooser_widget_init        (GdmUserChooserWidget      *user_chooser_widget);
+static void     gdm_user_chooser_widget_finalize    (GObject                   *object);
+
+G_DEFINE_TYPE (GdmUserChooserWidget, gdm_user_chooser_widget, GDM_TYPE_CHOOSER_WIDGET)
+
+static int
+get_font_height_for_widget (GtkWidget *widget)
+{
+        PangoFontMetrics *metrics;
+        PangoContext     *context;
+        int               ascent;
+        int               descent;
+        int               height;
+
+        gtk_widget_ensure_style (widget);
+        context = gtk_widget_get_pango_context (widget);
+        metrics = pango_context_get_metrics (context,
+                                             widget->style->font_desc,
+                                             pango_context_get_language (context));
+
+        ascent = pango_font_metrics_get_ascent (metrics);
+        descent = pango_font_metrics_get_descent (metrics);
+        height = PANGO_PIXELS (ascent + descent);
+        pango_font_metrics_unref (metrics);
+        return height;
+}
+
+static int
+get_icon_height_for_widget (GtkWidget *widget)
+{
+        int font_height;
+        int height;
+
+        font_height = get_font_height_for_widget (widget);
+        height = 3 * font_height;
+        if (height > MAX_ICON_SIZE) {
+                height = MAX_ICON_SIZE;
+        }
+
+        g_debug ("GdmUserChooserWidget: font height %d; using icon size %d", font_height, height);
+
+        return height;
+}
+
+static void
+add_user_other (GdmUserChooserWidget *widget)
+{
+        gdm_chooser_widget_add_item (GDM_CHOOSER_WIDGET (widget),
+                                     GDM_USER_CHOOSER_USER_OTHER,
+                                     NULL,
+                                     _("Other..."),
+                                     _("Choose a different account"),
+                                     0,
+                                     FALSE,
+                                     TRUE);
+}
+
+static void
+add_user_guest (GdmUserChooserWidget *widget)
+{
+        gdm_chooser_widget_add_item (GDM_CHOOSER_WIDGET (widget),
+                                     GDM_USER_CHOOSER_USER_GUEST,
+                                     widget->priv->stock_person_pixbuf,
+                                     _("Guest"),
+                                     _("Login as a temporary guest"),
+                                     0,
+                                     FALSE,
+                                     TRUE);
+}
+
+static void
+add_user_auto (GdmUserChooserWidget *widget)
+{
+        gdm_chooser_widget_add_item (GDM_CHOOSER_WIDGET (widget),
+                                     GDM_USER_CHOOSER_USER_AUTO,
+                                     NULL,
+                                     _("Automatic Login"),
+                                     _("Automatically login to the system after selecting options"),
+                                     0,
+                                     FALSE,
+                                     TRUE);
+}
+
+static void
+remove_user_other (GdmUserChooserWidget *widget)
+{
+        gdm_chooser_widget_remove_item (GDM_CHOOSER_WIDGET (widget),
+                                        GDM_USER_CHOOSER_USER_OTHER);
+}
+
+static void
+remove_user_guest (GdmUserChooserWidget *widget)
+{
+        gdm_chooser_widget_remove_item (GDM_CHOOSER_WIDGET (widget),
+                                        GDM_USER_CHOOSER_USER_GUEST);
+}
+
+static void
+remove_user_auto (GdmUserChooserWidget *widget)
+{
+        gdm_chooser_widget_remove_item (GDM_CHOOSER_WIDGET (widget),
+                                        GDM_USER_CHOOSER_USER_AUTO);
+}
+
+void
+gdm_user_chooser_widget_set_show_user_other (GdmUserChooserWidget *widget,
+                                             gboolean              show_user)
+{
+        g_return_if_fail (GDM_IS_USER_CHOOSER_WIDGET (widget));
+
+        if (widget->priv->show_user_other != show_user) {
+                widget->priv->show_user_other = show_user;
+                if (show_user) {
+                        add_user_other (widget);
+                } else {
+                        remove_user_other (widget);
+                }
+        }
+}
+
+void
+gdm_user_chooser_widget_set_show_user_guest (GdmUserChooserWidget *widget,
+                                             gboolean              show_user)
+{
+        g_return_if_fail (GDM_IS_USER_CHOOSER_WIDGET (widget));
+
+        if (widget->priv->show_user_guest != show_user) {
+                widget->priv->show_user_guest = show_user;
+                if (show_user) {
+                        add_user_guest (widget);
+                } else {
+                        remove_user_guest (widget);
+                }
+        }
+}
+
+void
+gdm_user_chooser_widget_set_show_user_auto (GdmUserChooserWidget *widget,
+                                            gboolean              show_user)
+{
+        g_return_if_fail (GDM_IS_USER_CHOOSER_WIDGET (widget));
+
+        if (widget->priv->show_user_auto != show_user) {
+                widget->priv->show_user_auto = show_user;
+                if (show_user) {
+                        add_user_auto (widget);
+                } else {
+                        remove_user_auto (widget);
+                }
+        }
+}
+
+char *
+gdm_user_chooser_widget_get_chosen_user_name (GdmUserChooserWidget *widget)
+{
+        g_return_val_if_fail (GDM_IS_USER_CHOOSER_WIDGET (widget), NULL);
+
+        return gdm_chooser_widget_get_active_item (GDM_CHOOSER_WIDGET (widget));
+}
+
+void
+gdm_user_chooser_widget_set_chosen_user_name (GdmUserChooserWidget *widget,
+                                              const char           *name)
+{
+        g_return_if_fail (GDM_IS_USER_CHOOSER_WIDGET (widget));
+
+        gdm_chooser_widget_set_active_item (GDM_CHOOSER_WIDGET (widget), name);
+}
+
+void
+gdm_user_chooser_widget_set_show_only_chosen (GdmUserChooserWidget *widget,
+                                              gboolean              show_only) {
+        g_return_if_fail (GDM_IS_USER_CHOOSER_WIDGET (widget));
+
+        gdm_chooser_widget_set_hide_inactive_items (GDM_CHOOSER_WIDGET (widget),
+                                                    show_only);
+
+}
+static void
+gdm_user_chooser_widget_set_property (GObject        *object,
+                                      guint           prop_id,
+                                      const GValue   *value,
+                                      GParamSpec     *pspec)
+{
+        GdmUserChooserWidget *self;
+
+        self = GDM_USER_CHOOSER_WIDGET (object);
+
+        switch (prop_id) {
+        case PROP_SHOW_USER_AUTO:
+                gdm_user_chooser_widget_set_show_user_auto (self, g_value_get_boolean (value));
+                break;
+        case PROP_SHOW_USER_GUEST:
+                gdm_user_chooser_widget_set_show_user_guest (self, g_value_get_boolean (value));
+                break;
+        case PROP_SHOW_USER_OTHER:
+                gdm_user_chooser_widget_set_show_user_other (self, g_value_get_boolean (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gdm_user_chooser_widget_get_property (GObject        *object,
+                                      guint           prop_id,
+                                      GValue         *value,
+                                      GParamSpec     *pspec)
+{
+        GdmUserChooserWidget *self;
+
+        self = GDM_USER_CHOOSER_WIDGET (object);
+
+        switch (prop_id) {
+        case PROP_SHOW_USER_AUTO:
+                g_value_set_boolean (value, self->priv->show_user_auto);
+                break;
+        case PROP_SHOW_USER_GUEST:
+                g_value_set_boolean (value, self->priv->show_user_guest);
+                break;
+        case PROP_SHOW_USER_OTHER:
+                g_value_set_boolean (value, self->priv->show_user_other);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static gboolean
+is_user_list_disabled (GdmUserChooserWidget *widget)
+{
+        GConfClient *client;
+        GError      *error;
+        gboolean     result;
+
+        client = gconf_client_get_default ();
+        error = NULL;
+        result = gconf_client_get_bool (client, KEY_DISABLE_USER_LIST, &error);
+        if (error != NULL) {
+                g_debug ("GdmUserChooserWidget: unable to get disable-user-list configuration: %s", error->message);
+                g_error_free (error);
+        }
+        g_object_unref (client);
+
+        return result;
+}
+
+static void
+add_user (GdmUserChooserWidget *widget,
+          GdmUser              *user)
+{
+        GdkPixbuf    *pixbuf;
+        char         *tooltip;
+        gboolean      is_logged_in;
+        int           size;
+
+        if (!widget->priv->show_normal_users) {
+                return;
+        }
+
+        size = get_icon_height_for_widget (widget);
+        pixbuf = gdm_user_render_icon (user, size);
+        if (pixbuf == NULL && widget->priv->stock_person_pixbuf != NULL) {
+                pixbuf = g_object_ref (widget->priv->stock_person_pixbuf);
+        }
+
+        tooltip = g_strdup_printf (_("Log in as %s"),
+                                   gdm_user_get_user_name (user));
+
+        is_logged_in = gdm_user_get_num_sessions (user) > 0;
+
+        g_debug ("GdmUserChooserWidget: User added name:%s logged-in:%d pixbuf:%p",
+                 gdm_user_get_user_name (user),
+                 is_logged_in,
+                 pixbuf);
+
+        gdm_chooser_widget_add_item (GDM_CHOOSER_WIDGET (widget),
+                                     gdm_user_get_user_name (user),
+                                     pixbuf,
+                                     gdm_user_get_real_name (user),
+                                     tooltip,
+                                     gdm_user_get_login_frequency (user),
+                                     is_logged_in,
+                                     FALSE);
+        g_free (tooltip);
+
+        if (pixbuf != NULL) {
+                g_object_unref (pixbuf);
+        }
+}
+
+static void
+on_user_added (GdmUserManager       *manager,
+               GdmUser              *user,
+               GdmUserChooserWidget *widget)
+{
+        /* wait for all users to be loaded */
+        if (! widget->priv->loaded) {
+                return;
+        }
+        add_user (widget, user);
+}
+
+static void
+on_user_removed (GdmUserManager       *manager,
+                 GdmUser              *user,
+                 GdmUserChooserWidget *widget)
+{
+        const char *user_name;
+
+        g_debug ("GdmUserChooserWidget: User removed: %s", gdm_user_get_user_name (user));
+        /* wait for all users to be loaded */
+        if (! widget->priv->loaded) {
+                return;
+        }
+
+        user_name = gdm_user_get_user_name (user);
+
+        gdm_chooser_widget_remove_item (GDM_CHOOSER_WIDGET (widget),
+                                        user_name);
+}
+
+static void
+on_user_is_logged_in_changed (GdmUserManager       *manager,
+                              GdmUser              *user,
+                              GdmUserChooserWidget *widget)
+{
+        const char *user_name;
+        gboolean    is_logged_in;
+
+        g_debug ("GdmUserChooserWidget: User logged in changed: %s", gdm_user_get_user_name (user));
+
+        user_name = gdm_user_get_user_name (user);
+        is_logged_in = gdm_user_get_num_sessions (user) > 0;
+
+        gdm_chooser_widget_set_item_in_use (GDM_CHOOSER_WIDGET (widget),
+                                            user_name,
+                                            is_logged_in);
+}
+
+static void
+on_user_login_frequency_changed (GdmUserManager       *manager,
+                                 GdmUser              *user,
+                                 GdmUserChooserWidget *widget)
+{
+        const char *user_name;
+        gulong      freq;
+
+        g_debug ("GdmUserChooserWidget: User login frequency changed: %s", gdm_user_get_user_name (user));
+
+        user_name = gdm_user_get_user_name (user);
+        freq = gdm_user_get_login_frequency (user);
+
+        gdm_chooser_widget_set_item_priority (GDM_CHOOSER_WIDGET (widget),
+                                              user_name,
+                                              freq);
+}
+
+static void
+on_users_loaded (GdmUserManager       *manager,
+                 GdmUserChooserWidget *widget)
+{
+        GSList *users;
+
+        widget->priv->loaded = TRUE;
+
+        g_debug ("GdmUserChooserWidget: Users loaded");
+
+        users = gdm_user_manager_list_users (manager);
+        while (users != NULL) {
+                add_user (widget, users->data);
+                users = g_slist_delete_link (users, users);
+        }
+
+        gtk_widget_grab_focus (GTK_WIDGET (widget));
+
+        gdm_chooser_widget_loaded (GDM_CHOOSER_WIDGET (widget));
+}
+
+static gboolean
+load_users (GdmUserChooserWidget *widget)
+{
+
+        if (widget->priv->show_normal_users) {
+                widget->priv->manager = gdm_user_manager_ref_default ();
+                g_signal_connect (widget->priv->manager,
+                                  "user-added",
+                                  G_CALLBACK (on_user_added),
+                                  widget);
+                g_signal_connect (widget->priv->manager,
+                                  "user-removed",
+                                  G_CALLBACK (on_user_removed),
+                                  widget);
+                g_signal_connect (widget->priv->manager,
+                                  "users-loaded",
+                                  G_CALLBACK (on_users_loaded),
+                                  widget);
+                g_signal_connect (widget->priv->manager,
+                                  "user-is-logged-in-changed",
+                                  G_CALLBACK (on_user_is_logged_in_changed),
+                                  widget);
+                g_signal_connect (widget->priv->manager,
+                                  "user-login-frequency-changed",
+                                  G_CALLBACK (on_user_login_frequency_changed),
+                                  widget);
+        } else {
+                gdm_chooser_widget_loaded (GDM_CHOOSER_WIDGET (widget));
+        }
+
+        widget->priv->load_idle_id = 0;
+
+        return FALSE;
+}
+
+static GObject *
+gdm_user_chooser_widget_constructor (GType                  type,
+                                     guint                  n_construct_properties,
+                                     GObjectConstructParam *construct_properties)
+{
+        GdmUserChooserWidget      *widget;
+
+        widget = GDM_USER_CHOOSER_WIDGET (G_OBJECT_CLASS (gdm_user_chooser_widget_parent_class)->constructor (type,
+                                                                                                              n_construct_properties,
+                                                                                                              construct_properties));
+
+        widget->priv->show_normal_users = !is_user_list_disabled (widget);
+
+        widget->priv->load_idle_id = g_idle_add ((GSourceFunc)load_users, widget);
+
+        return G_OBJECT (widget);
+}
+
+static void
+gdm_user_chooser_widget_dispose (GObject *object)
+{
+        GdmUserChooserWidget *widget;
+
+        widget = GDM_USER_CHOOSER_WIDGET (object);
+
+        G_OBJECT_CLASS (gdm_user_chooser_widget_parent_class)->dispose (object);
+
+        if (widget->priv->load_idle_id > 0) {
+                g_source_remove (widget->priv->load_idle_id);
+                widget->priv->load_idle_id = 0;
+        }
+
+        if (widget->priv->logged_in_pixbuf != NULL) {
+                g_object_unref (widget->priv->logged_in_pixbuf);
+                widget->priv->logged_in_pixbuf = NULL;
+        }
+
+        if (widget->priv->stock_person_pixbuf != NULL) {
+                g_object_unref (widget->priv->stock_person_pixbuf);
+                widget->priv->stock_person_pixbuf = NULL;
+        }
+}
+
+static void
+gdm_user_chooser_widget_class_init (GdmUserChooserWidgetClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->get_property = gdm_user_chooser_widget_get_property;
+        object_class->set_property = gdm_user_chooser_widget_set_property;
+        object_class->constructor = gdm_user_chooser_widget_constructor;
+        object_class->dispose = gdm_user_chooser_widget_dispose;
+        object_class->finalize = gdm_user_chooser_widget_finalize;
+
+
+        g_object_class_install_property (object_class,
+                                         PROP_SHOW_USER_AUTO,
+                                         g_param_spec_boolean ("show-user-auto",
+                                                               "show user auto",
+                                                               "show user auto",
+                                                               FALSE,
+                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+        g_object_class_install_property (object_class,
+                                         PROP_SHOW_USER_GUEST,
+                                         g_param_spec_boolean ("show-user-guest",
+                                                               "show user guest",
+                                                               "show user guest",
+                                                               FALSE,
+                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+        g_object_class_install_property (object_class,
+                                         PROP_SHOW_USER_OTHER,
+                                         g_param_spec_boolean ("show-user-other",
+                                                               "show user other",
+                                                               "show user other",
+                                                               TRUE,
+                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+        g_type_class_add_private (klass, sizeof (GdmUserChooserWidgetPrivate));
+}
+
+static GdkPixbuf *
+get_stock_person_pixbuf (GdmUserChooserWidget *widget)
+{
+        GdkPixbuf *pixbuf;
+        int        size;
+
+        size = get_icon_height_for_widget (widget);
+
+        pixbuf = gtk_icon_theme_load_icon (widget->priv->icon_theme,
+                                           DEFAULT_USER_ICON,
+                                           size,
+                                           0,
+                                           NULL);
+
+        return pixbuf;
+}
+
+static GdkPixbuf *
+get_logged_in_pixbuf (GdmUserChooserWidget *widget)
+{
+        GdkPixbuf *pixbuf;
+        int        size;
+
+        size = get_icon_height_for_widget (widget);
+
+        pixbuf = gtk_icon_theme_load_icon (widget->priv->icon_theme,
+                                           "emblem-default",
+                                           size / 3,
+                                           0,
+                                           NULL);
+
+        return pixbuf;
+}
+
+typedef struct {
+        GdkPixbuf *old_icon;
+        GdkPixbuf *new_icon;
+} IconUpdateData;
+
+static gboolean
+update_icons (GdmChooserWidget *widget,
+              const char       *id,
+              GdkPixbuf       **image,
+              char            **name,
+              char            **comment,
+              gulong           *priority,
+              gboolean         *is_in_use,
+              gboolean         *is_separate,
+              IconUpdateData   *data)
+{
+        if (data->old_icon == *image) {
+                *image = data->new_icon;
+                return TRUE;
+        }
+
+        return FALSE;
+}
+
+static void
+load_icons (GdmUserChooserWidget *widget)
+{
+        GdkPixbuf     *old_pixbuf;
+        IconUpdateData data;
+
+        if (widget->priv->logged_in_pixbuf != NULL) {
+                g_object_unref (widget->priv->logged_in_pixbuf);
+        }
+        widget->priv->logged_in_pixbuf = get_logged_in_pixbuf (widget);
+
+        old_pixbuf = widget->priv->stock_person_pixbuf;
+        widget->priv->stock_person_pixbuf = get_stock_person_pixbuf (widget);
+        /* update the icons in the model */
+        data.old_icon = old_pixbuf;
+        data.new_icon = widget->priv->stock_person_pixbuf;
+        gdm_chooser_widget_update_foreach_item (GDM_CHOOSER_WIDGET (widget),
+                                                (GdmChooserUpdateForeachFunc)update_icons,
+                                                &data);
+        if (old_pixbuf != NULL) {
+                g_object_unref (old_pixbuf);
+        }
+}
+
+static void
+on_icon_theme_changed (GtkIconTheme         *icon_theme,
+                       GdmUserChooserWidget *widget)
+{
+        g_debug ("GdmUserChooserWidget: icon theme changed");
+        load_icons (widget);
+}
+
+static void
+setup_icons (GdmUserChooserWidget *widget)
+{
+        if (gtk_widget_has_screen (GTK_WIDGET (widget))) {
+                widget->priv->icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (widget)));
+        } else {
+                widget->priv->icon_theme = gtk_icon_theme_get_default ();
+        }
+
+        if (widget->priv->icon_theme != NULL) {
+                g_signal_connect (widget->priv->icon_theme,
+                                  "changed",
+                                  G_CALLBACK (on_icon_theme_changed),
+                                  widget);
+        }
+
+        load_icons (widget);
+}
+
+static void
+gdm_user_chooser_widget_init (GdmUserChooserWidget *widget)
+{
+        widget->priv = GDM_USER_CHOOSER_WIDGET_GET_PRIVATE (widget);
+
+        gdm_chooser_widget_set_separator_position (GDM_CHOOSER_WIDGET (widget),
+                                                   GDM_CHOOSER_WIDGET_POSITION_BOTTOM);
+        gdm_chooser_widget_set_in_use_message (GDM_CHOOSER_WIDGET (widget),
+                                               _("Currently logged in"));
+
+        setup_icons (widget);
+}
+
+static void
+gdm_user_chooser_widget_finalize (GObject *object)
+{
+        GdmUserChooserWidget *widget;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_USER_CHOOSER_WIDGET (object));
+
+        widget = GDM_USER_CHOOSER_WIDGET (object);
+
+        g_return_if_fail (widget->priv != NULL);
+
+        G_OBJECT_CLASS (gdm_user_chooser_widget_parent_class)->finalize (object);
+}
+
+GtkWidget *
+gdm_user_chooser_widget_new (void)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_USER_CHOOSER_WIDGET,
+                               NULL);
+
+        return GTK_WIDGET (object);
+}

Added: trunk/src/gdmuser/gdm-user-chooser-widget.h
==============================================================================
--- (empty file)
+++ trunk/src/gdmuser/gdm-user-chooser-widget.h	Wed Feb  4 18:45:38 2009
@@ -0,0 +1,70 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
+ *
+ * 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 __GDM_USER_CHOOSER_WIDGET_H
+#define __GDM_USER_CHOOSER_WIDGET_H
+
+#include <glib-object.h>
+
+#include "gdm-chooser-widget.h"
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_USER_CHOOSER_WIDGET         (gdm_user_chooser_widget_get_type ())
+#define GDM_USER_CHOOSER_WIDGET(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_USER_CHOOSER_WIDGET, GdmUserChooserWidget))
+#define GDM_USER_CHOOSER_WIDGET_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_USER_CHOOSER_WIDGET, GdmUserChooserWidgetClass))
+#define GDM_IS_USER_CHOOSER_WIDGET(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_USER_CHOOSER_WIDGET))
+#define GDM_IS_USER_CHOOSER_WIDGET_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_USER_CHOOSER_WIDGET))
+#define GDM_USER_CHOOSER_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_USER_CHOOSER_WIDGET, GdmUserChooserWidgetClass))
+
+typedef struct GdmUserChooserWidgetPrivate GdmUserChooserWidgetPrivate;
+
+typedef struct
+{
+        GdmChooserWidget            parent;
+        GdmUserChooserWidgetPrivate *priv;
+} GdmUserChooserWidget;
+
+typedef struct
+{
+        GdmChooserWidgetClass   parent_class;
+} GdmUserChooserWidgetClass;
+
+#define GDM_USER_CHOOSER_USER_OTHER "__other"
+#define GDM_USER_CHOOSER_USER_GUEST "__guest"
+#define GDM_USER_CHOOSER_USER_AUTO  "__auto"
+
+GType                  gdm_user_chooser_widget_get_type                   (void);
+GtkWidget *            gdm_user_chooser_widget_new                        (void);
+
+char *                 gdm_user_chooser_widget_get_chosen_user_name       (GdmUserChooserWidget *widget);
+void                   gdm_user_chooser_widget_set_chosen_user_name       (GdmUserChooserWidget *widget,
+                                                                           const char           *user_name);
+void                   gdm_user_chooser_widget_set_show_only_chosen       (GdmUserChooserWidget *widget,
+                                                                           gboolean              show_only);
+void                   gdm_user_chooser_widget_set_show_user_other        (GdmUserChooserWidget *widget,
+                                                                           gboolean              show);
+void                   gdm_user_chooser_widget_set_show_user_guest        (GdmUserChooserWidget *widget,
+                                                                           gboolean              show);
+void                   gdm_user_chooser_widget_set_show_user_auto         (GdmUserChooserWidget *widget,
+                                                                           gboolean              show);
+G_END_DECLS
+
+#endif /* __GDM_USER_CHOOSER_WIDGET_H */

Added: trunk/src/gdmuser/gdm-user-manager.c
==============================================================================
--- (empty file)
+++ trunk/src/gdmuser/gdm-user-manager.c	Wed Feb  4 18:45:38 2009
@@ -0,0 +1,1644 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007-2008 William Jon McCann <mccann jhu edu>
+ *
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif /* HAVE_PATHS_H */
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include "gdm-user-manager.h"
+#include "gdm-user-private.h"
+
+#define GDM_USER_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_USER_MANAGER, GdmUserManagerPrivate))
+
+#define CK_NAME      "org.freedesktop.ConsoleKit"
+#define CK_PATH      "/org/freedesktop/ConsoleKit"
+#define CK_INTERFACE "org.freedesktop.ConsoleKit"
+
+#define CK_MANAGER_PATH      "/org/freedesktop/ConsoleKit/Manager"
+#define CK_MANAGER_INTERFACE "org.freedesktop.ConsoleKit.Manager"
+#define CK_SEAT_INTERFACE    "org.freedesktop.ConsoleKit.Seat"
+#define CK_SESSION_INTERFACE "org.freedesktop.ConsoleKit.Session"
+
+/* Prefs Defaults */
+#define DEFAULT_ALLOW_ROOT      TRUE
+#define DEFAULT_MAX_ICON_SIZE   128
+#define DEFAULT_USER_MAX_FILE   65536
+
+#ifdef __sun
+#define DEFAULT_MINIMAL_UID     100
+#else
+#define DEFAULT_MINIMAL_UID     500
+#endif
+
+#ifndef _PATH_SHELLS
+#define _PATH_SHELLS    "/etc/shells"
+#endif
+#define PATH_PASSWD     "/etc/passwd"
+
+#define DEFAULT_GLOBAL_FACE_DIR DATADIR "/faces"
+#define DEFAULT_USER_ICON       "stock_person"
+#define DEFAULT_EXCLUDE         { "bin",        \
+                                  "root",       \
+                                  "daemon",     \
+                                  "adm",        \
+                                  "lp",         \
+                                  "sync",       \
+                                  "shutdown",   \
+                                  "halt",       \
+                                  "mail",       \
+                                  "news",       \
+                                  "uucp",       \
+                                  "operator",   \
+                                  "nobody",     \
+                                  "gdm",        \
+                                  "postgres",   \
+                                  "pvm",        \
+                                  "rpm",        \
+                                  "nfsnobody",  \
+                                  "pcap",       \
+                                  NULL }
+
+struct GdmUserManagerPrivate
+{
+        GHashTable            *users;
+        GHashTable            *sessions;
+        GHashTable            *exclusions;
+        GHashTable            *shells;
+        DBusGConnection       *connection;
+        DBusGProxy            *seat_proxy;
+        char                  *seat_id;
+
+        GFileMonitor          *passwd_monitor;
+        GFileMonitor          *shells_monitor;
+
+        guint                  reload_id;
+        guint                  ck_history_id;
+
+        guint8                 users_dirty : 1;
+};
+
+enum {
+        LOADING_USERS,
+        USERS_LOADED,
+        USER_ADDED,
+        USER_REMOVED,
+        USER_IS_LOGGED_IN_CHANGED,
+        USER_LOGIN_FREQUENCY_CHANGED,
+        LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void     gdm_user_manager_class_init (GdmUserManagerClass *klass);
+static void     gdm_user_manager_init       (GdmUserManager      *user_manager);
+static void     gdm_user_manager_finalize   (GObject             *object);
+
+static gpointer user_manager_object = NULL;
+
+G_DEFINE_TYPE (GdmUserManager, gdm_user_manager, G_TYPE_OBJECT)
+
+GQuark
+gdm_user_manager_error_quark (void)
+{
+        static GQuark ret = 0;
+        if (ret == 0) {
+                ret = g_quark_from_static_string ("gdm_user_manager_error");
+        }
+
+        return ret;
+}
+
+static gboolean
+start_new_login_session (GdmUserManager *manager)
+{
+        GError  *error;
+        gboolean res;
+
+        res = g_spawn_command_line_async ("gdmflexiserver -s", &error);
+        if (! res) {
+                g_warning ("Unable to start new login: %s", error->message);
+                g_error_free (error);
+        }
+
+        return res;
+}
+
+/* needs to stay in sync with gdm-slave */
+static char *
+_get_primary_user_session_id (GdmUserManager *manager,
+                              GdmUser        *user)
+{
+        gboolean    res;
+        gboolean    can_activate_sessions;
+        GError     *error;
+        GList      *sessions;
+        GList      *l;
+        char       *primary_ssid;
+
+        if (manager->priv->seat_id == NULL || manager->priv->seat_id[0] == '\0') {
+                g_debug ("GdmUserManager: display seat id is not set; can't switch sessions");
+                return NULL;
+        }
+
+        primary_ssid = NULL;
+        sessions = NULL;
+
+        g_debug ("GdmUserManager: checking if seat can activate sessions");
+
+        error = NULL;
+        res = dbus_g_proxy_call (manager->priv->seat_proxy,
+                                 "CanActivateSessions",
+                                 &error,
+                                 G_TYPE_INVALID,
+                                 G_TYPE_BOOLEAN, &can_activate_sessions,
+                                 G_TYPE_INVALID);
+        if (! res) {
+                g_warning ("unable to determine if seat can activate sessions: %s",
+                           error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        if (! can_activate_sessions) {
+                g_debug ("GdmUserManager: seat is unable to activate sessions");
+                goto out;
+        }
+
+        sessions = gdm_user_get_sessions (user);
+        if (sessions == NULL) {
+                g_warning ("unable to determine sessions for user: %s",
+                           gdm_user_get_user_name (user));
+                goto out;
+        }
+
+        for (l = sessions; l != NULL; l = l->next) {
+                const char *ssid;
+
+                ssid = l->data;
+
+                /* FIXME: better way to choose? */
+                if (ssid != NULL) {
+                        primary_ssid = g_strdup (ssid);
+                        break;
+                }
+        }
+
+ out:
+
+        return primary_ssid;
+}
+
+static gboolean
+activate_session_id (GdmUserManager *manager,
+                     const char     *seat_id,
+                     const char     *session_id)
+{
+        DBusError    local_error;
+        DBusMessage *message;
+        DBusMessage *reply;
+        gboolean     ret;
+
+        ret = FALSE;
+        reply = NULL;
+
+        dbus_error_init (&local_error);
+        message = dbus_message_new_method_call ("org.freedesktop.ConsoleKit",
+                                                seat_id,
+                                                "org.freedesktop.ConsoleKit.Seat",
+                                                "ActivateSession");
+        if (message == NULL) {
+                goto out;
+        }
+
+        if (! dbus_message_append_args (message,
+                                        DBUS_TYPE_OBJECT_PATH, &session_id,
+                                        DBUS_TYPE_INVALID)) {
+                goto out;
+        }
+
+
+        dbus_error_init (&local_error);
+        reply = dbus_connection_send_with_reply_and_block (dbus_g_connection_get_connection (manager->priv->connection),
+                                                           message,
+                                                           -1,
+                                                           &local_error);
+        if (reply == NULL) {
+                if (dbus_error_is_set (&local_error)) {
+                        g_warning ("Unable to activate session: %s", local_error.message);
+                        dbus_error_free (&local_error);
+                        goto out;
+                }
+        }
+
+        ret = TRUE;
+ out:
+        if (message != NULL) {
+                dbus_message_unref (message);
+        }
+        if (reply != NULL) {
+                dbus_message_unref (reply);
+        }
+
+        return ret;
+}
+
+static gboolean
+session_is_login_window (GdmUserManager *manager,
+                         const char     *session_id)
+{
+        DBusGProxy      *proxy;
+        GError          *error;
+        gboolean         res;
+        gboolean         ret;
+        char            *session_type;
+
+        ret = FALSE;
+
+        proxy = dbus_g_proxy_new_for_name (manager->priv->connection,
+                                           CK_NAME,
+                                           session_id,
+                                           CK_SESSION_INTERFACE);
+        if (proxy == NULL) {
+                g_warning ("Failed to connect to the ConsoleKit seat object");
+                goto out;
+        }
+
+        session_type = NULL;
+        error = NULL;
+        res = dbus_g_proxy_call (proxy,
+                                 "GetSessionType",
+                                 &error,
+                                 G_TYPE_INVALID,
+                                 G_TYPE_STRING, &session_type,
+                                 G_TYPE_INVALID);
+        if (! res) {
+                g_debug ("Failed to identify the session type: %s", error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        if (session_type == NULL || session_type[0] == '\0' || strcmp (session_type, "LoginWindow") != 0) {
+                goto out;
+        }
+
+        ret = TRUE;
+
+ out:
+        if (proxy != NULL) {
+                g_object_unref (proxy);
+        }
+
+        return ret;
+}
+
+static char *
+_get_login_window_session_id (GdmUserManager *manager)
+{
+        gboolean    res;
+        gboolean    can_activate_sessions;
+        GError     *error;
+        GPtrArray  *sessions;
+        char       *primary_ssid;
+        int         i;
+
+        if (manager->priv->seat_id == NULL || manager->priv->seat_id[0] == '\0') {
+                g_debug ("GdmUserManager: display seat id is not set; can't switch sessions");
+                return NULL;
+        }
+
+        primary_ssid = NULL;
+        sessions = NULL;
+
+        g_debug ("GdmSlave: checking if seat can activate sessions");
+
+        error = NULL;
+        res = dbus_g_proxy_call (manager->priv->seat_proxy,
+                                 "CanActivateSessions",
+                                 &error,
+                                 G_TYPE_INVALID,
+                                 G_TYPE_BOOLEAN, &can_activate_sessions,
+                                 G_TYPE_INVALID);
+        if (! res) {
+                g_warning ("unable to determine if seat can activate sessions: %s",
+                           error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        if (! can_activate_sessions) {
+                g_debug ("GdmSlave: seat is unable to activate sessions");
+                goto out;
+        }
+
+        error = NULL;
+        res = dbus_g_proxy_call (manager->priv->seat_proxy,
+                                 "GetSessions",
+                                 &error,
+                                 G_TYPE_INVALID,
+                                 dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &sessions,
+                                 G_TYPE_INVALID);
+        if (! res) {
+                g_warning ("unable to determine sessions for user: %s",
+                           error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        for (i = 0; i < sessions->len; i++) {
+                char *ssid;
+
+                ssid = g_ptr_array_index (sessions, i);
+
+                if (session_is_login_window (manager, ssid)) {
+                        primary_ssid = g_strdup (ssid);
+                        break;
+                }
+        }
+        g_ptr_array_foreach (sessions, (GFunc)g_free, NULL);
+        g_ptr_array_free (sessions, TRUE);
+
+ out:
+
+        return primary_ssid;
+}
+
+gboolean
+gdm_user_manager_goto_login_session (GdmUserManager *manager)
+{
+        gboolean ret;
+        gboolean res;
+        char    *ssid;
+
+        g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), FALSE);
+
+        ret = FALSE;
+
+        /* First look for any existing LoginWindow sessions on the seat.
+           If none are found, create a new one. */
+
+        ssid = _get_login_window_session_id (manager);
+        if (ssid != NULL) {
+                res = activate_session_id (manager, manager->priv->seat_id, ssid);
+                if (res) {
+                        ret = TRUE;
+                }
+        }
+
+        if (! ret) {
+                res = start_new_login_session (manager);
+                if (res) {
+                        ret = TRUE;
+                }
+        }
+
+        return ret;
+}
+
+gboolean
+gdm_user_manager_activate_user_session (GdmUserManager *manager,
+                                        GdmUser        *user)
+{
+        gboolean ret;
+        char    *ssid;
+        gboolean res;
+
+        g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), FALSE);
+        g_return_val_if_fail (GDM_IS_USER (user), FALSE);
+
+        ret = FALSE;
+
+        ssid = _get_primary_user_session_id (manager, user);
+        if (ssid == NULL) {
+                goto out;
+        }
+
+        res = activate_session_id (manager, manager->priv->seat_id, ssid);
+        if (! res) {
+                g_debug ("GdmUserManager: unable to activate session: %s", ssid);
+                goto out;
+        }
+
+        ret = TRUE;
+ out:
+        return ret;
+}
+
+static void
+on_user_sessions_changed (GdmUser        *user,
+                          GdmUserManager *manager)
+{
+        guint nsessions;
+
+        nsessions = gdm_user_get_num_sessions (user);
+
+        g_debug ("GdmUserManager: sessions changed user=%s num=%d",
+                 gdm_user_get_user_name (user),
+                 nsessions);
+
+        /* only signal on zero and one */
+        if (nsessions > 1) {
+                return;
+        }
+
+        g_signal_emit (manager, signals [USER_IS_LOGGED_IN_CHANGED], 0, user);
+}
+
+static void
+on_user_icon_changed (GdmUser        *user,
+                      GdmUserManager *manager)
+{
+        g_debug ("GdmUserManager: user icon changed");
+}
+
+static char *
+get_seat_id_for_session (DBusGConnection *connection,
+                         const char      *session_id)
+{
+        DBusGProxy      *proxy;
+        GError          *error;
+        char            *seat_id;
+        gboolean         res;
+
+        proxy = NULL;
+        seat_id = NULL;
+
+        proxy = dbus_g_proxy_new_for_name (connection,
+                                           CK_NAME,
+                                           session_id,
+                                           CK_SESSION_INTERFACE);
+        if (proxy == NULL) {
+                g_warning ("Failed to connect to the ConsoleKit session object");
+                goto out;
+        }
+
+        error = NULL;
+        res = dbus_g_proxy_call (proxy,
+                                 "GetSeatId",
+                                 &error,
+                                 G_TYPE_INVALID,
+                                 DBUS_TYPE_G_OBJECT_PATH, &seat_id,
+                                 G_TYPE_INVALID);
+        if (! res) {
+                g_debug ("Failed to identify the current seat: %s", error->message);
+                g_error_free (error);
+        }
+ out:
+        if (proxy != NULL) {
+                g_object_unref (proxy);
+        }
+
+        return seat_id;
+}
+
+static char *
+get_x11_display_for_session (DBusGConnection *connection,
+                             const char      *session_id)
+{
+        DBusGProxy      *proxy;
+        GError          *error;
+        char            *x11_display;
+        gboolean         res;
+
+        proxy = NULL;
+        x11_display = NULL;
+
+        proxy = dbus_g_proxy_new_for_name (connection,
+                                           CK_NAME,
+                                           session_id,
+                                           CK_SESSION_INTERFACE);
+        if (proxy == NULL) {
+                g_warning ("Failed to connect to the ConsoleKit session object");
+                goto out;
+        }
+
+        error = NULL;
+        res = dbus_g_proxy_call (proxy,
+                                 "GetX11Display",
+                                 &error,
+                                 G_TYPE_INVALID,
+                                 G_TYPE_STRING, &x11_display,
+                                 G_TYPE_INVALID);
+        if (! res) {
+                g_debug ("Failed to identify the x11 display: %s", error->message);
+                g_error_free (error);
+        }
+ out:
+        if (proxy != NULL) {
+                g_object_unref (proxy);
+        }
+
+        return x11_display;
+}
+
+static gboolean
+maybe_add_session_for_user (GdmUserManager *manager,
+                            GdmUser        *user,
+                            const char     *ssid)
+{
+        char    *sid;
+        char    *x11_display;
+        gboolean ret;
+
+        ret = FALSE;
+        sid = NULL;
+        x11_display = NULL;
+
+        /* skip if on another seat */
+        sid = get_seat_id_for_session (manager->priv->connection, ssid);
+        if (sid == NULL
+            || manager->priv->seat_id == NULL
+            || strcmp (sid, manager->priv->seat_id) != 0) {
+                g_debug ("GdmUserManager: not adding session on other seat: %s", ssid);
+                goto out;
+        }
+
+        /* skip if doesn't have an x11 display */
+        x11_display = get_x11_display_for_session (manager->priv->connection, ssid);
+        if (x11_display == NULL || x11_display[0] == '\0') {
+                g_debug ("GdmUserManager: not adding session without a x11 display: %s", ssid);
+                goto out;
+        }
+
+        if (g_hash_table_lookup (manager->priv->exclusions, gdm_user_get_user_name (user))) {
+                g_debug ("GdmUserManager: excluding user '%s'", gdm_user_get_user_name (user));
+                goto out;
+        }
+
+        g_hash_table_insert (manager->priv->sessions,
+                             g_strdup (ssid),
+                             g_strdup (gdm_user_get_user_name (user)));
+
+        _gdm_user_add_session (user, ssid);
+        g_debug ("GdmUserManager: added session for user: %s", gdm_user_get_user_name (user));
+
+        ret = TRUE;
+
+ out:
+        g_free (sid);
+        g_free (x11_display);
+
+        return ret;
+}
+
+static void
+add_sessions_for_user (GdmUserManager *manager,
+                       GdmUser        *user)
+{
+        DBusGProxy      *proxy;
+        GError          *error;
+        gboolean         res;
+        guint32          uid;
+        GPtrArray       *sessions;
+        int              i;
+
+        proxy = dbus_g_proxy_new_for_name (manager->priv->connection,
+                                           CK_NAME,
+                                           CK_MANAGER_PATH,
+                                           CK_MANAGER_INTERFACE);
+        if (proxy == NULL) {
+                g_warning ("Failed to connect to the ConsoleKit manager object");
+                goto out;
+        }
+
+        uid = gdm_user_get_uid (user);
+
+        g_debug ("Getting list of sessions for user %u", uid);
+
+        error = NULL;
+        res = dbus_g_proxy_call (proxy,
+                                 "GetSessionsForUnixUser",
+                                 &error,
+                                 G_TYPE_UINT, uid,
+                                 G_TYPE_INVALID,
+                                 dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH),
+                                 &sessions,
+                                 G_TYPE_INVALID);
+        if (! res) {
+                g_debug ("Failed to find sessions for user: %s", error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        g_debug ("Found %d sessions for user %s", sessions->len, gdm_user_get_user_name (user));
+
+        for (i = 0; i < sessions->len; i++) {
+                char *ssid;
+
+                ssid = g_ptr_array_index (sessions, i);
+                maybe_add_session_for_user (manager, user, ssid);
+        }
+
+        g_ptr_array_foreach (sessions, (GFunc)g_free, NULL);
+        g_ptr_array_free (sessions, TRUE);
+
+ out:
+        if (proxy != NULL) {
+                g_object_unref (proxy);
+        }
+}
+
+static GdmUser *
+create_user (GdmUserManager *manager)
+{
+        GdmUser *user;
+
+        user = g_object_new (GDM_TYPE_USER, "manager", manager, NULL);
+        g_signal_connect (user,
+                          "sessions-changed",
+                          G_CALLBACK (on_user_sessions_changed),
+                          manager);
+        g_signal_connect (user,
+                          "icon-changed",
+                          G_CALLBACK (on_user_icon_changed),
+                          manager);
+        return user;
+}
+
+static void
+add_user (GdmUserManager *manager,
+          GdmUser        *user)
+{
+        add_sessions_for_user (manager, user);
+        g_hash_table_insert (manager->priv->users,
+                             g_strdup (gdm_user_get_user_name (user)),
+                             g_object_ref (user));
+
+        g_signal_emit (manager, signals[USER_ADDED], 0, user);
+}
+
+static GdmUser *
+add_new_user_for_pwent (GdmUserManager *manager,
+                        struct passwd  *pwent)
+{
+        GdmUser *user;
+
+        g_debug ("Creating new user");
+
+        user = create_user (manager);
+        _gdm_user_update (user, pwent);
+
+        add_user (manager, user);
+
+        return user;
+}
+
+static char *
+get_current_seat_id (DBusGConnection *connection)
+{
+        DBusGProxy      *proxy;
+        GError          *error;
+        char            *session_id;
+        char            *seat_id;
+        gboolean         res;
+
+        proxy = NULL;
+        session_id = NULL;
+        seat_id = NULL;
+
+        proxy = dbus_g_proxy_new_for_name (connection,
+                                           CK_NAME,
+                                           CK_MANAGER_PATH,
+                                           CK_MANAGER_INTERFACE);
+        if (proxy == NULL) {
+                g_warning ("Failed to connect to the ConsoleKit manager object");
+                goto out;
+        }
+
+        error = NULL;
+        res = dbus_g_proxy_call (proxy,
+                                 "GetCurrentSession",
+                                 &error,
+                                 G_TYPE_INVALID,
+                                 DBUS_TYPE_G_OBJECT_PATH,
+                                 &session_id,
+                                 G_TYPE_INVALID);
+        if (! res) {
+                g_debug ("Failed to identify the current session: %s", error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        seat_id = get_seat_id_for_session (connection, session_id);
+
+ out:
+        if (proxy != NULL) {
+                g_object_unref (proxy);
+        }
+        g_free (session_id);
+
+        return seat_id;
+}
+
+static gboolean
+get_uid_from_session_id (GdmUserManager *manager,
+                         const char     *session_id,
+                         uid_t          *uidp)
+{
+        DBusGProxy      *proxy;
+        GError          *error;
+        guint            uid;
+        gboolean         res;
+
+        proxy = dbus_g_proxy_new_for_name (manager->priv->connection,
+                                           CK_NAME,
+                                           session_id,
+                                           CK_SESSION_INTERFACE);
+        if (proxy == NULL) {
+                g_warning ("Failed to connect to the ConsoleKit session object");
+                return FALSE;
+        }
+
+        error = NULL;
+        res = dbus_g_proxy_call (proxy,
+                                 "GetUnixUser",
+                                 &error,
+                                 G_TYPE_INVALID,
+                                 G_TYPE_UINT, &uid,
+                                 G_TYPE_INVALID);
+        g_object_unref (proxy);
+
+        if (! res) {
+                g_warning ("Failed to query the session: %s", error->message);
+                g_error_free (error);
+                return FALSE;
+        }
+
+        if (uidp != NULL) {
+                *uidp = (uid_t) uid;
+        }
+
+        return TRUE;
+}
+
+static void
+seat_session_added (DBusGProxy     *seat_proxy,
+                    const char     *session_id,
+                    GdmUserManager *manager)
+{
+        uid_t          uid;
+        gboolean       res;
+        struct passwd *pwent;
+        GdmUser       *user;
+        gboolean       is_new;
+
+        g_debug ("Session added: %s", session_id);
+
+        res = get_uid_from_session_id (manager, session_id, &uid);
+        if (! res) {
+                g_warning ("Unable to lookup user for session");
+                return;
+        }
+
+        errno = 0;
+        pwent = getpwuid (uid);
+        if (pwent == NULL) {
+                g_warning ("Unable to lookup user id %d: %s", (int)uid, g_strerror (errno));
+                return;
+        }
+
+        /* check exclusions up front */
+        if (g_hash_table_lookup (manager->priv->exclusions, pwent->pw_name)) {
+                g_debug ("GdmUserManager: excluding user '%s'", pwent->pw_name);
+                return;
+        }
+
+        user = g_hash_table_lookup (manager->priv->users, pwent->pw_name);
+        if (user == NULL) {
+                g_debug ("Creating new user");
+
+                user = create_user (manager);
+                _gdm_user_update (user, pwent);
+                is_new = TRUE;
+        } else {
+                is_new = FALSE;
+        }
+
+        res = maybe_add_session_for_user (manager, user, session_id);
+
+        /* only add the user if we added a session */
+        if (is_new) {
+                if (res) {
+                        add_user (manager, user);
+                } else {
+                        g_object_unref (user);
+                }
+        }
+}
+
+static void
+seat_session_removed (DBusGProxy     *seat_proxy,
+                      const char     *session_id,
+                      GdmUserManager *manager)
+{
+        GdmUser *user;
+        char    *username;
+
+        g_debug ("Session removed: %s", session_id);
+
+        /* since the session object may already be gone
+         * we can't query CK directly */
+
+        username = g_hash_table_lookup (manager->priv->sessions, session_id);
+        if (username == NULL) {
+                return;
+        }
+
+        user = g_hash_table_lookup (manager->priv->users, username);
+        if (user == NULL) {
+                /* nothing to do */
+                return;
+        }
+
+        g_debug ("GdmUserManager: Session removed for %s", username);
+        _gdm_user_remove_session (user, session_id);
+}
+
+static void
+on_proxy_destroy (DBusGProxy     *proxy,
+                  GdmUserManager *manager)
+{
+        g_debug ("GdmUserManager: seat proxy destroyed");
+
+        manager->priv->seat_proxy = NULL;
+}
+
+static void
+get_seat_proxy (GdmUserManager *manager)
+{
+        DBusGProxy      *proxy;
+        GError          *error;
+
+        g_assert (manager->priv->seat_proxy == NULL);
+
+        error = NULL;
+        manager->priv->connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
+        if (manager->priv->connection == NULL) {
+                g_warning ("Failed to connect to the D-Bus daemon: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        manager->priv->seat_id = get_current_seat_id (manager->priv->connection);
+        if (manager->priv->seat_id == NULL) {
+                return;
+        }
+
+        g_debug ("GdmUserManager: Found current seat: %s", manager->priv->seat_id);
+
+        error = NULL;
+        proxy = dbus_g_proxy_new_for_name_owner (manager->priv->connection,
+                                                 CK_NAME,
+                                                 manager->priv->seat_id,
+                                                 CK_SEAT_INTERFACE,
+                                                 &error);
+
+        if (proxy == NULL) {
+                g_warning ("Failed to connect to the ConsoleKit seat object: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        g_signal_connect (proxy, "destroy", G_CALLBACK (on_proxy_destroy), manager);
+
+        dbus_g_proxy_add_signal (proxy,
+                                 "SessionAdded",
+                                 DBUS_TYPE_G_OBJECT_PATH,
+                                 G_TYPE_INVALID);
+        dbus_g_proxy_add_signal (proxy,
+                                 "SessionRemoved",
+                                 DBUS_TYPE_G_OBJECT_PATH,
+                                 G_TYPE_INVALID);
+        dbus_g_proxy_connect_signal (proxy,
+                                     "SessionAdded",
+                                     G_CALLBACK (seat_session_added),
+                                     manager,
+                                     NULL);
+        dbus_g_proxy_connect_signal (proxy,
+                                     "SessionRemoved",
+                                     G_CALLBACK (seat_session_removed),
+                                     manager,
+                                     NULL);
+        manager->priv->seat_proxy = proxy;
+
+}
+
+/**
+ * gdm_manager_get_user:
+ * @manager: the manager to query.
+ * @username: the login name of the user to get.
+ *
+ * Retrieves a pointer to the #GdmUser object for the login named @username
+ * from @manager. This pointer is not a reference, and should not be released.
+ *
+ * Returns: a pointer to a #GdmUser object.
+ **/
+GdmUser *
+gdm_user_manager_get_user (GdmUserManager *manager,
+                           const char     *username)
+{
+        GdmUser *user;
+
+        g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), NULL);
+        g_return_val_if_fail (username != NULL && username[0] != '\0', NULL);
+
+        user = g_hash_table_lookup (manager->priv->users, username);
+
+        if (user == NULL) {
+                struct passwd *pwent;
+
+                pwent = getpwnam (username);
+
+                if (pwent != NULL) {
+                        user = add_new_user_for_pwent (manager, pwent);
+                }
+        }
+
+        return user;
+}
+
+GdmUser *
+gdm_user_manager_get_user_by_uid (GdmUserManager *manager,
+                                  uid_t           uid)
+{
+        GdmUser       *user;
+        struct passwd *pwent;
+
+        g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), NULL);
+
+        pwent = getpwuid (uid);
+        if (pwent == NULL) {
+                g_warning ("GdmUserManager: unable to lookup uid %d", (int)uid);
+                return NULL;
+        }
+
+        user = g_hash_table_lookup (manager->priv->users, pwent->pw_name);
+
+        if (user == NULL) {
+                user = add_new_user_for_pwent (manager, pwent);
+        }
+
+        return user;
+}
+
+static void
+listify_hash_values_hfunc (gpointer key,
+                           gpointer value,
+                           gpointer user_data)
+{
+        GSList **list = user_data;
+
+        *list = g_slist_prepend (*list, value);
+}
+
+GSList *
+gdm_user_manager_list_users (GdmUserManager *manager)
+{
+        GSList *retval;
+
+        g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), NULL);
+
+        retval = NULL;
+        g_hash_table_foreach (manager->priv->users, listify_hash_values_hfunc, &retval);
+
+        return g_slist_sort (retval, (GCompareFunc) gdm_user_collate);
+}
+
+static gboolean
+parse_value_as_ulong (const char *value,
+                      gulong     *ulongval)
+{
+        char  *end_of_valid_long;
+        glong  long_value;
+        gulong ulong_value;
+
+        errno = 0;
+        long_value = strtol (value, &end_of_valid_long, 10);
+
+        if (*value == '\0' || *end_of_valid_long != '\0') {
+                return FALSE;
+        }
+
+        ulong_value = long_value;
+        if (ulong_value != long_value || errno == ERANGE) {
+                return FALSE;
+        }
+
+        *ulongval = ulong_value;
+
+        return TRUE;
+}
+
+static gboolean
+parse_ck_history_line (const char *line,
+                       char      **user_namep,
+                       gulong     *frequencyp)
+{
+        GRegex     *re;
+        GMatchInfo *match_info;
+        gboolean    res;
+        gboolean    ret;
+        GError     *error;
+
+        ret = FALSE;
+        re = NULL;
+        match_info = NULL;
+
+        error = NULL;
+        re = g_regex_new ("(?P<username>[0-9a-zA-Z]+)[ ]+(?P<frequency>[0-9]+)", 0, 0, &error);
+        if (re == NULL) {
+                g_critical ("%s", error->message);
+                goto out;
+        }
+
+        g_regex_match (re, line, 0, &match_info);
+
+        res = g_match_info_matches (match_info);
+        if (! res) {
+                g_warning ("Unable to parse history: %s", line);
+                goto out;
+        }
+
+        if (user_namep != NULL) {
+                *user_namep = g_match_info_fetch_named (match_info, "username");
+        }
+
+        if (frequencyp != NULL) {
+                char *freq;
+                freq = g_match_info_fetch_named (match_info, "frequency");
+                res = parse_value_as_ulong (freq, frequencyp);
+                g_free (freq);
+                if (! res) {
+                        goto out;
+                }
+        }
+
+        ret = TRUE;
+
+ out:
+        if (match_info != NULL) {
+                g_match_info_free (match_info);
+        }
+        if (re != NULL) {
+                g_regex_unref (re);
+        }
+        return ret;
+}
+
+static void
+process_ck_history_line (GdmUserManager *manager,
+                         const char     *line)
+{
+        gboolean res;
+        char    *username;
+        gulong   frequency;
+        GdmUser *user;
+
+        frequency = 0;
+        username = NULL;
+        res = parse_ck_history_line (line, &username, &frequency);
+        if (! res) {
+                return;
+        }
+
+        if (g_hash_table_lookup (manager->priv->exclusions, username)) {
+                g_debug ("GdmUserManager: excluding user '%s'", username);
+                g_free (username);
+                return;
+        }
+
+        user = gdm_user_manager_get_user (manager, username);
+        if (user == NULL) {
+                g_debug ("GdmUserManager: unable to lookup user '%s'", username);
+                g_free (username);
+                return;
+        }
+
+        g_object_set (user, "login-frequency", frequency, NULL);
+        g_signal_emit (manager, signals [USER_LOGIN_FREQUENCY_CHANGED], 0, user);
+        g_free (username);
+}
+
+static gboolean
+ck_history_watch (GIOChannel     *source,
+                  GIOCondition    condition,
+                  GdmUserManager *manager)
+{
+        GIOStatus status;
+        gboolean  done  = FALSE;
+
+        g_return_val_if_fail (manager != NULL, FALSE);
+
+        if (condition & G_IO_IN) {
+                char   *str;
+                GError *error;
+
+                error = NULL;
+                status = g_io_channel_read_line (source, &str, NULL, NULL, &error);
+                if (error != NULL) {
+                        g_warning ("GdmUserManager: unable to read line: %s", error->message);
+                        g_error_free (error);
+                }
+
+                if (status == G_IO_STATUS_NORMAL) {
+                        g_debug ("GdmUserManager: history output: %s", str);
+                        process_ck_history_line (manager, str);
+                } else if (status == G_IO_STATUS_EOF) {
+                        done = TRUE;
+                }
+
+                g_free (str);
+        } else if (condition & G_IO_HUP) {
+                done = TRUE;
+        }
+
+        if (done) {
+                g_signal_emit (G_OBJECT (manager), signals[USERS_LOADED], 0);
+
+                manager->priv->ck_history_id = 0;
+                return FALSE;
+        }
+
+        return TRUE;
+}
+
+static void
+reload_ck_history (GdmUserManager *manager)
+{
+        char       *command;
+        const char *seat_id;
+        GError     *error;
+        gboolean    res;
+        char      **argv;
+        int         standard_out;
+        GIOChannel *channel;
+
+        seat_id = NULL;
+        if (manager->priv->seat_id != NULL
+            && g_str_has_prefix (manager->priv->seat_id, "/org/freedesktop/ConsoleKit/")) {
+
+                seat_id = manager->priv->seat_id + strlen ("/org/freedesktop/ConsoleKit/");
+        }
+
+        if (seat_id == NULL) {
+                g_warning ("Unable to find users: no seat-id found");
+                return;
+        }
+
+        command = g_strdup_printf ("ck-history --frequent --seat='%s' --session-type=''",
+                                   seat_id);
+        g_debug ("GdmUserManager: running '%s'", command);
+        error = NULL;
+        if (! g_shell_parse_argv (command, NULL, &argv, &error)) {
+                g_warning ("Could not parse command: %s", error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        error = NULL;
+        res = g_spawn_async_with_pipes (NULL,
+                                        argv,
+                                        NULL,
+                                        G_SPAWN_SEARCH_PATH,
+                                        NULL,
+                                        NULL,
+                                        NULL, /* pid */
+                                        NULL,
+                                        &standard_out,
+                                        NULL,
+                                        &error);
+        g_strfreev (argv);
+        if (! res) {
+                g_warning ("Unable to run ck-history: %s", error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        channel = g_io_channel_unix_new (standard_out);
+        g_io_channel_set_close_on_unref (channel, TRUE);
+        g_io_channel_set_flags (channel,
+                                g_io_channel_get_flags (channel) | G_IO_FLAG_NONBLOCK,
+                                NULL);
+        manager->priv->ck_history_id = g_io_add_watch (channel,
+                                                       G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+                                                       (GIOFunc)ck_history_watch,
+                                                       manager);
+        g_io_channel_unref (channel);
+
+ out:
+        g_free (command);
+}
+
+static void
+reload_passwd (GdmUserManager *manager)
+{
+        struct passwd *pwent;
+        GSList        *old_users;
+        GSList        *new_users;
+        GSList        *list;
+        FILE          *fp;
+
+        old_users = NULL;
+        new_users = NULL;
+
+        errno = 0;
+        fp = fopen (PATH_PASSWD, "r");
+        if (fp == NULL) {
+                g_warning ("Unable to open %s: %s", PATH_PASSWD, g_strerror (errno));
+                goto out;
+        }
+
+        g_hash_table_foreach (manager->priv->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 (gdm_user_get_num_sessions (list->data) > 0) {
+                        g_object_freeze_notify (G_OBJECT (list->data));
+                        new_users = g_slist_prepend (new_users, g_object_ref (list->data));
+                }
+        }
+
+        for (pwent = fgetpwent (fp); pwent != NULL; pwent = fgetpwent (fp)) {
+                GdmUser *user;
+
+                user = NULL;
+
+                /* Skip users below MinimalUID... */
+                if (pwent->pw_uid < DEFAULT_MINIMAL_UID) {
+                        continue;
+                }
+
+                /* ...And users w/ invalid shells... */
+                if (pwent->pw_shell == NULL ||
+                    !g_hash_table_lookup (manager->priv->shells, pwent->pw_shell)) {
+                        g_debug ("GdmUserManager: skipping user with bad shell: %s", pwent->pw_name);
+                        continue;
+                }
+
+                /* ...And explicitly excluded users */
+                if (g_hash_table_lookup (manager->priv->exclusions, pwent->pw_name)) {
+                        g_debug ("GdmUserManager: explicitly skipping user: %s", pwent->pw_name);
+                        continue;
+                }
+
+                user = g_hash_table_lookup (manager->priv->users, pwent->pw_name);
+
+                /* Update users already in the *new* list */
+                if (g_slist_find (new_users, user)) {
+                        _gdm_user_update (user, pwent);
+                        continue;
+                }
+
+                if (user == NULL) {
+                        user = create_user (manager);
+                } else {
+                        g_object_ref (user);
+                }
+
+                /* Freeze & update users not already in the new list */
+                g_object_freeze_notify (G_OBJECT (user));
+                _gdm_user_update (user, pwent);
+
+                new_users = g_slist_prepend (new_users, user);
+        }
+
+        /* Go through and handle added users */
+        for (list = new_users; list; list = list->next) {
+                if (! g_slist_find (old_users, list->data)) {
+                        add_user (manager, 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->priv->users,
+                                             gdm_user_get_user_name (list->data));
+                }
+        }
+
+ out:
+        /* Cleanup */
+
+        fclose (fp);
+
+        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);
+}
+
+static void
+reload_users (GdmUserManager *manager)
+{
+        reload_ck_history (manager);
+        reload_passwd (manager);
+}
+
+static gboolean
+reload_users_timeout (GdmUserManager *manager)
+{
+        reload_users (manager);
+        manager->priv->reload_id = 0;
+
+        return FALSE;
+}
+
+static void
+queue_reload_users (GdmUserManager *manager)
+{
+        if (manager->priv->reload_id > 0) {
+                return;
+        }
+
+        g_signal_emit (G_OBJECT (manager), signals[LOADING_USERS], 0);
+        manager->priv->reload_id = g_idle_add ((GSourceFunc)reload_users_timeout, manager);
+}
+
+static void
+reload_shells (GdmUserManager *manager)
+{
+        char *shell;
+
+        setusershell ();
+
+        g_hash_table_remove_all (manager->priv->shells);
+        for (shell = getusershell (); shell != NULL; shell = getusershell ()) {
+                /* skip well known not-real shells */
+                if (shell == NULL
+                    || strcmp (shell, "/sbin/nologin") == 0
+                    || strcmp (shell, "/bin/false") == 0) {
+                        g_debug ("GdmUserManager: skipping shell %s", shell);
+                        continue;
+                }
+                g_hash_table_insert (manager->priv->shells,
+                                     g_strdup (shell),
+                                     GUINT_TO_POINTER (TRUE));
+        }
+
+        endusershell ();
+}
+
+static void
+on_shells_monitor_changed (GFileMonitor     *monitor,
+                           GFile            *file,
+                           GFile            *other_file,
+                           GFileMonitorEvent event_type,
+                           GdmUserManager   *manager)
+{
+        if (event_type != G_FILE_MONITOR_EVENT_CHANGED &&
+            event_type != G_FILE_MONITOR_EVENT_CREATED) {
+                return;
+        }
+
+        reload_shells (manager);
+        reload_passwd (manager);
+}
+
+static void
+on_passwd_monitor_changed (GFileMonitor     *monitor,
+                           GFile            *file,
+                           GFile            *other_file,
+                           GFileMonitorEvent event_type,
+                           GdmUserManager   *manager)
+{
+        if (event_type != G_FILE_MONITOR_EVENT_CHANGED &&
+            event_type != G_FILE_MONITOR_EVENT_CREATED) {
+                return;
+        }
+
+        reload_passwd (manager);
+}
+
+static void
+gdm_user_manager_class_init (GdmUserManagerClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gdm_user_manager_finalize;
+
+        signals [LOADING_USERS] =
+                g_signal_new ("loading-users",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmUserManagerClass, loading_users),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+        signals [USERS_LOADED] =
+                g_signal_new ("users-loaded",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmUserManagerClass, users_loaded),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+        signals [USER_ADDED] =
+                g_signal_new ("user-added",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmUserManagerClass, user_added),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__OBJECT,
+                              G_TYPE_NONE, 1, GDM_TYPE_USER);
+        signals [USER_REMOVED] =
+                g_signal_new ("user-removed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmUserManagerClass, user_removed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__OBJECT,
+                              G_TYPE_NONE, 1, GDM_TYPE_USER);
+        signals [USER_IS_LOGGED_IN_CHANGED] =
+                g_signal_new ("user-is-logged-in-changed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmUserManagerClass, user_is_logged_in_changed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__OBJECT,
+                              G_TYPE_NONE, 1, GDM_TYPE_USER);
+        signals [USER_LOGIN_FREQUENCY_CHANGED] =
+                g_signal_new ("user-login-frequency-changed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmUserManagerClass, user_login_frequency_changed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__OBJECT,
+                              G_TYPE_NONE, 1, GDM_TYPE_USER);
+
+        g_type_class_add_private (klass, sizeof (GdmUserManagerPrivate));
+}
+
+static void
+gdm_user_manager_init (GdmUserManager *manager)
+{
+        int            i;
+        GFile         *file;
+        GError        *error;
+        const char    *exclude_default[] = DEFAULT_EXCLUDE;
+
+        manager->priv = GDM_USER_MANAGER_GET_PRIVATE (manager);
+
+        /* sessions */
+        manager->priv->sessions = g_hash_table_new_full (g_str_hash,
+                                                         g_str_equal,
+                                                         g_free,
+                                                         g_free);
+
+        /* exclusions */
+        manager->priv->exclusions = g_hash_table_new_full (g_str_hash,
+                                                           g_str_equal,
+                                                           g_free,
+                                                           NULL);
+        for (i = 0; exclude_default[i] != NULL; i++) {
+                g_hash_table_insert (manager->priv->exclusions,
+                                     g_strdup (exclude_default [i]),
+                                     GUINT_TO_POINTER (TRUE));
+        }
+
+        /* /etc/shells */
+        manager->priv->shells = g_hash_table_new_full (g_str_hash,
+                                                       g_str_equal,
+                                                       g_free,
+                                                       NULL);
+        reload_shells (manager);
+        file = g_file_new_for_path (_PATH_SHELLS);
+        error = NULL;
+        manager->priv->shells_monitor = g_file_monitor_file (file,
+                                                             G_FILE_MONITOR_NONE,
+                                                             NULL,
+                                                             &error);
+        if (manager->priv->shells_monitor != NULL) {
+                g_signal_connect (manager->priv->shells_monitor,
+                                  "changed",
+                                  G_CALLBACK (on_shells_monitor_changed),
+                                  manager);
+        } else {
+                g_warning ("Unable to monitor %s: %s", _PATH_SHELLS, error->message);
+                g_error_free (error);
+        }
+        g_object_unref (file);
+
+        /* /etc/passwd */
+        manager->priv->users = g_hash_table_new_full (g_str_hash,
+                                                      g_str_equal,
+                                                      g_free,
+                                                      (GDestroyNotify) g_object_run_dispose);
+        file = g_file_new_for_path (PATH_PASSWD);
+        manager->priv->passwd_monitor = g_file_monitor_file (file,
+                                                             G_FILE_MONITOR_NONE,
+                                                             NULL,
+                                                             &error);
+        if (manager->priv->passwd_monitor != NULL) {
+                g_signal_connect (manager->priv->passwd_monitor,
+                                  "changed",
+                                  G_CALLBACK (on_passwd_monitor_changed),
+                                  manager);
+        } else {
+                g_warning ("Unable to monitor %s: %s", PATH_PASSWD, error->message);
+                g_error_free (error);
+        }
+        g_object_unref (file);
+
+
+        get_seat_proxy (manager);
+
+        queue_reload_users (manager);
+
+        manager->priv->users_dirty = FALSE;
+}
+
+static void
+gdm_user_manager_finalize (GObject *object)
+{
+        GdmUserManager *manager;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_USER_MANAGER (object));
+
+        manager = GDM_USER_MANAGER (object);
+
+        g_return_if_fail (manager->priv != NULL);
+
+        if (manager->priv->seat_proxy != NULL) {
+                g_object_unref (manager->priv->seat_proxy);
+        }
+
+        if (manager->priv->ck_history_id != 0) {
+                g_source_remove (manager->priv->ck_history_id);
+                manager->priv->ck_history_id = 0;
+        }
+
+        if (manager->priv->reload_id > 0) {
+                g_source_remove (manager->priv->reload_id);
+                manager->priv->reload_id = 0;
+        }
+
+        g_hash_table_destroy (manager->priv->sessions);
+
+        g_file_monitor_cancel (manager->priv->passwd_monitor);
+        g_hash_table_destroy (manager->priv->users);
+
+        g_file_monitor_cancel (manager->priv->shells_monitor);
+        g_hash_table_destroy (manager->priv->shells);
+
+        g_free (manager->priv->seat_id);
+
+        G_OBJECT_CLASS (gdm_user_manager_parent_class)->finalize (object);
+}
+
+GdmUserManager *
+gdm_user_manager_ref_default (void)
+{
+        if (user_manager_object != NULL) {
+                g_object_ref (user_manager_object);
+        } else {
+                user_manager_object = g_object_new (GDM_TYPE_USER_MANAGER, NULL);
+                g_object_add_weak_pointer (user_manager_object,
+                                           (gpointer *) &user_manager_object);
+        }
+
+        return GDM_USER_MANAGER (user_manager_object);
+}

Added: trunk/src/gdmuser/gdm-user-manager.h
==============================================================================
--- (empty file)
+++ trunk/src/gdmuser/gdm-user-manager.h	Wed Feb  4 18:45:38 2009
@@ -0,0 +1,87 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
+ *
+ * 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 __GDM_USER_MANAGER_H
+#define __GDM_USER_MANAGER_H
+
+#include <glib-object.h>
+
+#include "gdm-user.h"
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_USER_MANAGER         (gdm_user_manager_get_type ())
+#define GDM_USER_MANAGER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_USER_MANAGER, GdmUserManager))
+#define GDM_USER_MANAGER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_USER_MANAGER, GdmUserManagerClass))
+#define GDM_IS_USER_MANAGER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_USER_MANAGER))
+#define GDM_IS_USER_MANAGER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_USER_MANAGER))
+#define GDM_USER_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_USER_MANAGER, GdmUserManagerClass))
+
+typedef struct GdmUserManagerPrivate GdmUserManagerPrivate;
+
+typedef struct
+{
+        GObject                parent;
+        GdmUserManagerPrivate *priv;
+} GdmUserManager;
+
+typedef struct
+{
+        GObjectClass   parent_class;
+
+        void          (* loading_users)             (GdmUserManager *user_manager);
+        void          (* users_loaded)              (GdmUserManager *user_manager);
+        void          (* user_added)                (GdmUserManager *user_manager,
+                                                     GdmUser        *user);
+        void          (* user_removed)              (GdmUserManager *user_manager,
+                                                     GdmUser        *user);
+        void          (* user_is_logged_in_changed) (GdmUserManager *user_manager,
+                                                     GdmUser        *user);
+        void          (* user_login_frequency_changed) (GdmUserManager *user_manager,
+                                                        GdmUser        *user);
+} GdmUserManagerClass;
+
+typedef enum
+{
+        GDM_USER_MANAGER_ERROR_GENERAL,
+        GDM_USER_MANAGER_ERROR_KEY_NOT_FOUND
+} GdmUserManagerError;
+
+#define GDM_USER_MANAGER_ERROR gdm_user_manager_error_quark ()
+
+GQuark              gdm_user_manager_error_quark           (void);
+GType               gdm_user_manager_get_type              (void);
+
+GdmUserManager *    gdm_user_manager_ref_default           (void);
+
+GSList *            gdm_user_manager_list_users            (GdmUserManager *manager);
+GdmUser *           gdm_user_manager_get_user              (GdmUserManager *manager,
+                                                            const char     *user_name);
+GdmUser *           gdm_user_manager_get_user_by_uid       (GdmUserManager *manager,
+                                                            uid_t           uid);
+
+gboolean            gdm_user_manager_activate_user_session (GdmUserManager *manager,
+                                                            GdmUser        *user);
+
+gboolean            gdm_user_manager_goto_login_session    (GdmUserManager *manager);
+
+G_END_DECLS
+
+#endif /* __GDM_USER_MANAGER_H */

Added: trunk/src/gdmuser/gdm-user-private.h
==============================================================================
--- (empty file)
+++ trunk/src/gdmuser/gdm-user-private.h	Wed Feb  4 18:45:38 2009
@@ -0,0 +1,44 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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 GdmUser object
+ */
+
+#ifndef __GDM_USER_PRIVATE__
+#define __GDM_USER_PRIVATE__ 1
+
+#include <pwd.h>
+
+#include "gdm-user.h"
+
+G_BEGIN_DECLS
+
+void _gdm_user_update           (GdmUser             *user,
+                                 const struct passwd *pwent);
+void _gdm_user_add_session      (GdmUser             *user,
+                                 const char          *session_id);
+void _gdm_user_remove_session   (GdmUser             *user,
+                                 const char          *session_id);
+
+void _gdm_user_icon_changed     (GdmUser             *user);
+
+G_END_DECLS
+
+#endif /* !__GDM_USER_PRIVATE__ */

Added: trunk/src/gdmuser/gdm-user.c
==============================================================================
--- (empty file)
+++ trunk/src/gdmuser/gdm-user.c	Wed Feb  4 18:45:38 2009
@@ -0,0 +1,1171 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2004-2005 James M. Cape <jcape ignore-your tv>.
+ * Copyright (C) 2007-2008 William Jon McCann <mccann jhu edu>
+ *
+ * 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 <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include "gdm-user-manager.h"
+#include "gdm-user-private.h"
+
+#define GDM_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_USER, GdmUserClass))
+#define GDM_IS_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDM_TYPE_USER))
+#define GDM_USER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS ((object), GDM_TYPE_USER, GdmUserClass))
+
+#define GLOBAL_FACEDIR    DATADIR "/faces"
+#define MAX_ICON_SIZE     128
+#define MAX_FILE_SIZE     65536
+#define MINIMAL_UID       100
+#define RELAX_GROUP       TRUE
+#define RELAX_OTHER       TRUE
+
+enum {
+        PROP_0,
+        PROP_MANAGER,
+        PROP_REAL_NAME,
+        PROP_USER_NAME,
+        PROP_UID,
+        PROP_HOME_DIR,
+        PROP_SHELL,
+        PROP_LOGIN_FREQUENCY,
+};
+
+enum {
+        ICON_CHANGED,
+        SESSIONS_CHANGED,
+        LAST_SIGNAL
+};
+
+struct _GdmUser {
+        GObject         parent;
+
+        GdmUserManager *manager;
+
+        uid_t           uid;
+        char           *user_name;
+        char           *real_name;
+        char           *home_dir;
+        char           *shell;
+        GList          *sessions;
+        gulong          login_frequency;
+
+        GFileMonitor   *icon_monitor;
+};
+
+typedef struct _GdmUserClass
+{
+        GObjectClass parent_class;
+
+        void (* icon_changed)     (GdmUser *user);
+        void (* sessions_changed) (GdmUser *user);
+} GdmUserClass;
+
+static void gdm_user_finalize     (GObject      *object);
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GdmUser, gdm_user, G_TYPE_OBJECT)
+
+static int
+session_compare (const char *a,
+                 const char *b)
+{
+        if (a == NULL) {
+                return 1;
+        } else if (b == NULL) {
+                return -1;
+        }
+
+        return strcmp (a, b);
+}
+
+void
+_gdm_user_add_session (GdmUser    *user,
+                       const char *ssid)
+{
+        GList *li;
+
+        g_return_if_fail (GDM_IS_USER (user));
+        g_return_if_fail (ssid != NULL);
+
+        li = g_list_find_custom (user->sessions, ssid, (GCompareFunc)session_compare);
+        if (li == NULL) {
+                g_debug ("GdmUser: adding session %s", ssid);
+                user->sessions = g_list_prepend (user->sessions, g_strdup (ssid));
+                g_signal_emit (user, signals[SESSIONS_CHANGED], 0);
+        } else {
+                g_debug ("GdmUser: session already present: %s", ssid);
+        }
+}
+
+void
+_gdm_user_remove_session (GdmUser    *user,
+                          const char *ssid)
+{
+        GList *li;
+
+        g_return_if_fail (GDM_IS_USER (user));
+        g_return_if_fail (ssid != NULL);
+
+        li = g_list_find_custom (user->sessions, ssid, (GCompareFunc)session_compare);
+        if (li != NULL) {
+                g_debug ("GdmUser: removing session %s", ssid);
+                g_free (li->data);
+                user->sessions = g_list_delete_link (user->sessions, li);
+                g_signal_emit (user, signals[SESSIONS_CHANGED], 0);
+        } else {
+                g_debug ("GdmUser: session not found: %s", ssid);
+        }
+}
+
+guint
+gdm_user_get_num_sessions (GdmUser    *user)
+{
+        return g_list_length (user->sessions);
+}
+
+GList *
+gdm_user_get_sessions (GdmUser *user)
+{
+        return user->sessions;
+}
+
+static void
+_gdm_user_set_login_frequency (GdmUser *user,
+                               gulong   login_frequency)
+{
+        user->login_frequency = login_frequency;
+        g_object_notify (G_OBJECT (user), "login-frequency");
+}
+
+static void
+gdm_user_set_property (GObject      *object,
+                       guint         param_id,
+                       const GValue *value,
+                       GParamSpec   *pspec)
+{
+        GdmUser *user;
+
+        user = GDM_USER (object);
+
+        switch (param_id) {
+        case PROP_MANAGER:
+                user->manager = g_value_get_object (value);
+                g_assert (user->manager);
+                break;
+        case PROP_LOGIN_FREQUENCY:
+                _gdm_user_set_login_frequency (user, g_value_get_ulong (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+                break;
+        }
+}
+
+static void
+gdm_user_get_property (GObject    *object,
+                       guint       param_id,
+                       GValue     *value,
+                       GParamSpec *pspec)
+{
+        GdmUser *user;
+
+        user = GDM_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->user_name);
+                break;
+        case PROP_REAL_NAME:
+                g_value_set_string (value, user->real_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_LOGIN_FREQUENCY:
+                g_value_set_ulong (value, user->login_frequency);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+                break;
+        }
+}
+
+static void
+gdm_user_class_init (GdmUserClass *class)
+{
+        GObjectClass *gobject_class;
+
+        gobject_class = G_OBJECT_CLASS (class);
+
+        gobject_class->set_property = gdm_user_set_property;
+        gobject_class->get_property = gdm_user_get_property;
+        gobject_class->finalize = gdm_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."),
+                                                              GDM_TYPE_USER_MANAGER,
+                                                              (G_PARAM_READWRITE |
+                                                               G_PARAM_CONSTRUCT_ONLY)));
+
+        g_object_class_install_property (gobject_class,
+                                         PROP_REAL_NAME,
+                                         g_param_spec_string ("real-name",
+                                                              "Real Name",
+                                                              "The real 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));
+        g_object_class_install_property (gobject_class,
+                                         PROP_LOGIN_FREQUENCY,
+                                         g_param_spec_ulong ("login-frequency",
+                                                             "login frequency",
+                                                             "login frequency",
+                                                             0,
+                                                             G_MAXULONG,
+                                                             0,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+        signals [ICON_CHANGED] =
+                g_signal_new ("icon-changed",
+                              G_TYPE_FROM_CLASS (class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmUserClass, icon_changed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+        signals [SESSIONS_CHANGED] =
+                g_signal_new ("sessions-changed",
+                              G_TYPE_FROM_CLASS (class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmUserClass, sessions_changed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+}
+
+
+static void
+on_icon_monitor_changed (GFileMonitor     *monitor,
+                         GFile            *file,
+                         GFile            *other_file,
+                         GFileMonitorEvent event_type,
+                         GdmUser          *user)
+{
+        g_debug ("Icon changed: %d", event_type);
+
+        if (event_type != G_FILE_MONITOR_EVENT_CHANGED &&
+            event_type != G_FILE_MONITOR_EVENT_CREATED) {
+                return;
+        }
+
+        _gdm_user_icon_changed (user);
+}
+
+static void
+update_icon_monitor (GdmUser *user)
+{
+        GFile  *file;
+        GError *error;
+        char   *path;
+
+        if (user->home_dir == NULL) {
+                return;
+        }
+
+        if (user->icon_monitor != NULL) {
+                g_file_monitor_cancel (user->icon_monitor);
+                user->icon_monitor = NULL;
+        }
+
+        path = g_build_filename (user->home_dir, ".face", NULL);
+        g_debug ("adding monitor for '%s'", path);
+        file = g_file_new_for_path (path);
+        error = NULL;
+        user->icon_monitor = g_file_monitor_file (file,
+                                                  G_FILE_MONITOR_NONE,
+                                                  NULL,
+                                                  &error);
+        if (user->icon_monitor != NULL) {
+                g_signal_connect (user->icon_monitor,
+                                  "changed",
+                                  G_CALLBACK (on_icon_monitor_changed),
+                                  user);
+        } else {
+                g_warning ("Unable to monitor %s: %s", path, error->message);
+                g_error_free (error);
+        }
+        g_object_unref (file);
+        g_free (path);
+}
+
+static void
+gdm_user_init (GdmUser *user)
+{
+        user->manager = NULL;
+        user->user_name = NULL;
+        user->real_name = NULL;
+        user->sessions = NULL;
+}
+
+static void
+gdm_user_finalize (GObject *object)
+{
+        GdmUser *user;
+
+        user = GDM_USER (object);
+
+        g_file_monitor_cancel (user->icon_monitor);
+
+        g_free (user->user_name);
+        g_free (user->real_name);
+
+        if (G_OBJECT_CLASS (gdm_user_parent_class)->finalize)
+                (*G_OBJECT_CLASS (gdm_user_parent_class)->finalize) (object);
+}
+
+/**
+ * _gdm_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
+_gdm_user_update (GdmUser             *user,
+                  const struct passwd *pwent)
+{
+        gchar *real_name;
+
+        g_return_if_fail (GDM_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) {
+                        real_name = g_strndup (pwent->pw_gecos,
+                                                  (first_comma - pwent->pw_gecos));
+                } else {
+                        real_name = g_strdup (pwent->pw_gecos);
+                }
+
+                if (real_name[0] == '\0') {
+                        g_free (real_name);
+                        real_name = NULL;
+                }
+        } else {
+                real_name = NULL;
+        }
+
+        if ((real_name && !user->real_name) ||
+            (!real_name && user->real_name) ||
+            (real_name &&
+             user->real_name &&
+             strcmp (real_name, user->real_name) != 0)) {
+                g_free (user->real_name);
+                user->real_name = real_name;
+                g_object_notify (G_OBJECT (user), "real-name");
+        } else {
+                g_free (real_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->user_name) ||
+            (!pwent->pw_name && user->user_name) ||
+            (pwent->pw_name &&
+             user->user_name &&
+             strcmp (user->user_name, pwent->pw_name) != 0)) {
+                g_free (user->user_name);
+                user->user_name = 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");
+        }
+
+        update_icon_monitor (user);
+
+        g_object_thaw_notify (G_OBJECT (user));
+}
+
+/**
+ * _gdm_user_icon_changed:
+ * @user: the user to emit the signal for.
+ *
+ * Emits the "icon-changed" signal for @user.
+ *
+ * Since: 1.0
+ **/
+void
+_gdm_user_icon_changed (GdmUser *user)
+{
+        g_return_if_fail (GDM_IS_USER (user));
+
+        g_signal_emit (user, signals[ICON_CHANGED], 0);
+}
+
+/**
+ * gdm_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
+gdm_user_get_uid (GdmUser *user)
+{
+        g_return_val_if_fail (GDM_IS_USER (user), -1);
+
+        return user->uid;
+}
+
+/**
+ * gdm_user_get_real_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 *
+gdm_user_get_real_name (GdmUser *user)
+{
+        g_return_val_if_fail (GDM_IS_USER (user), NULL);
+
+        return (user->real_name ? user->real_name : user->user_name);
+}
+
+/**
+ * gdm_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 *
+gdm_user_get_user_name (GdmUser *user)
+{
+        g_return_val_if_fail (GDM_IS_USER (user), NULL);
+
+        return user->user_name;
+}
+
+/**
+ * gdm_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 *
+gdm_user_get_home_directory (GdmUser *user)
+{
+        g_return_val_if_fail (GDM_IS_USER (user), NULL);
+
+        return user->home_dir;
+}
+
+/**
+ * gdm_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 *
+gdm_user_get_shell (GdmUser *user)
+{
+        g_return_val_if_fail (GDM_IS_USER (user), NULL);
+
+        return user->shell;
+}
+
+gulong
+gdm_user_get_login_frequency (GdmUser *user)
+{
+        g_return_val_if_fail (GDM_IS_USER (user), 0);
+
+        return user->login_frequency;
+}
+
+int
+gdm_user_collate (GdmUser *user1,
+                  GdmUser *user2)
+{
+        const char *str1;
+        const char *str2;
+        gulong      num1;
+        gulong      num2;
+
+        g_return_val_if_fail (user1 == NULL || GDM_IS_USER (user1), 0);
+        g_return_val_if_fail (user2 == NULL || GDM_IS_USER (user2), 0);
+
+        if (user1->real_name != NULL) {
+                str1 = user1->real_name;
+        } else {
+                str1 = user1->user_name;
+        }
+
+        if (user2->real_name != NULL) {
+                str2 = user2->real_name;
+        } else {
+                str2 = user2->user_name;
+        }
+
+        num1 = user1->login_frequency;
+        num2 = user2->login_frequency;
+        g_debug ("Login freq 1=%u 2=%u", (guint)num1, (guint)num2);
+        if (num1 > num2) {
+                return -1;
+        }
+
+        if (num1 < num2) {
+                return 1;
+        }
+
+        /* if login frequency is equal try names */
+        if (str1 == NULL && str2 != NULL) {
+                return -1;
+        }
+
+        if (str1 != NULL && str2 == NULL) {
+                return 1;
+        }
+
+        if (str1 == NULL && str2 == NULL) {
+                return 0;
+        }
+
+        return g_utf8_collate (str1, str2);
+}
+
+static gboolean
+check_user_file (const char *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 char *
+get_filesystem_type (const char *path)
+{
+        GFile      *file;
+        GFileInfo  *file_info;
+        GError     *error;
+        char       *filesystem_type;
+
+        file = g_file_new_for_path (path);
+        error = NULL;
+        file_info = g_file_query_filesystem_info (file,
+                                                  G_FILE_ATTRIBUTE_FILESYSTEM_TYPE,
+                                                  NULL,
+                                                  &error);
+        if (file_info == NULL) {
+                g_warning ("Unable to query filesystem type for %s: %s", path, error->message);
+                g_error_free (error);
+                g_object_unref (file);
+                return NULL;
+        }
+
+        filesystem_type = g_strdup (g_file_info_get_attribute_string (file_info,
+                                                                      G_FILE_ATTRIBUTE_FILESYSTEM_TYPE));
+        if (filesystem_type == NULL) {
+                g_warning ("GIO returned NULL filesystem type for %s", path);
+        }
+
+        g_object_unref (file);
+        g_object_unref (file_info);
+
+        return filesystem_type;
+}
+
+static GdkPixbuf *
+render_icon_from_home (GdmUser *user,
+                       int      icon_size)
+{
+        GdkPixbuf  *retval;
+        char       *path;
+        gboolean    is_local;
+        gboolean    is_autofs;
+        gboolean    res;
+        char       *filesystem_type;
+
+        is_local = FALSE;
+
+        /* 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 (user->home_dir);
+        filesystem_type = get_filesystem_type (path);
+        is_autofs = (filesystem_type != NULL && strcmp (filesystem_type, "autofs") == 0);
+        g_free (filesystem_type);
+        g_free (path);
+
+        if (is_autofs) {
+                return NULL;
+        }
+
+        /* now check that home dir itself is local */
+        filesystem_type = get_filesystem_type (user->home_dir);
+        is_local = ((filesystem_type != NULL) &&
+                    (strcmp (filesystem_type, "nfs") != 0) &&
+                    (strcmp (filesystem_type, "afs") != 0) &&
+                    (strcmp (filesystem_type, "autofs") != 0) &&
+                    (strcmp (filesystem_type, "unknown") != 0) &&
+                    (strcmp (filesystem_type, "ncpfs") != 0));
+        g_free (filesystem_type);
+
+        /* 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 (user->home_dir, ".face", NULL);
+        res = check_user_file (path,
+                               user->uid,
+                               MAX_FILE_SIZE,
+                               RELAX_GROUP,
+                               RELAX_OTHER);
+        if (res) {
+                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 == NULL) {
+                path = g_build_filename (user->home_dir,
+                                         ".face.icon",
+                                         NULL);
+                res = check_user_file (path,
+                                       user->uid,
+                                       MAX_FILE_SIZE,
+                                       RELAX_GROUP,
+                                       RELAX_OTHER);
+                if (res) {
+                        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 == NULL) {
+                path = g_build_filename (user->home_dir,
+                                         ".gnome",
+                                         "gdm",
+                                         NULL);
+                res = check_user_file (path,
+                                       user->uid,
+                                       MAX_FILE_SIZE,
+                                       RELAX_GROUP,
+                                       RELAX_OTHER);
+                if (res) {
+                        GKeyFile *keyfile;
+                        char     *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);
+                        res = check_user_file (icon_path,
+                                               user->uid,
+                                               MAX_FILE_SIZE,
+                                               RELAX_GROUP,
+                                               RELAX_OTHER);
+                        if (icon_path && res) {
+                                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);
+        }
+
+        return retval;
+}
+
+static void
+curved_rectangle (cairo_t *cr,
+                  double   x0,
+                  double   y0,
+                  double   width,
+                  double   height,
+                  double   radius)
+{
+        double x1;
+        double y1;
+
+        x1 = x0 + width;
+        y1 = y0 + height;
+
+        if (!width || !height) {
+                return;
+        }
+
+        if (width / 2 < radius) {
+                if (height / 2 < radius) {
+                        cairo_move_to  (cr, x0, (y0 + y1) / 2);
+                        cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0);
+                        cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2);
+                        cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1);
+                        cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2);
+                } else {
+                        cairo_move_to  (cr, x0, y0 + radius);
+                        cairo_curve_to (cr, x0, y0, x0, y0, (x0 + x1) / 2, y0);
+                        cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
+                        cairo_line_to (cr, x1, y1 - radius);
+                        cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1);
+                        cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - radius);
+                }
+        } else {
+                if (height / 2 < radius) {
+                        cairo_move_to  (cr, x0, (y0 + y1) / 2);
+                        cairo_curve_to (cr, x0, y0, x0 , y0, x0 + radius, y0);
+                        cairo_line_to (cr, x1 - radius, y0);
+                        cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2);
+                        cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
+                        cairo_line_to (cr, x0 + radius, y1);
+                        cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2);
+                } else {
+                        cairo_move_to  (cr, x0, y0 + radius);
+                        cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0);
+                        cairo_line_to (cr, x1 - radius, y0);
+                        cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
+                        cairo_line_to (cr, x1, y1 - radius);
+                        cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
+                        cairo_line_to (cr, x0 + radius, y1);
+                        cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - radius);
+                }
+        }
+
+        cairo_close_path (cr);
+}
+
+static cairo_surface_t *
+surface_from_pixbuf (GdkPixbuf *pixbuf)
+{
+        cairo_surface_t *surface;
+        cairo_t         *cr;
+
+        surface = cairo_image_surface_create (gdk_pixbuf_get_has_alpha (pixbuf) ?
+                                              CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
+                                              gdk_pixbuf_get_width (pixbuf),
+                                              gdk_pixbuf_get_height (pixbuf));
+        cr = cairo_create (surface);
+        gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+        cairo_paint (cr);
+        cairo_destroy (cr);
+
+        return surface;
+}
+
+/**
+ * go_cairo_convert_data_to_pixbuf:
+ * @src: a pointer to pixel data in cairo format
+ * @dst: a pointer to pixel data in pixbuf format
+ * @width: image width
+ * @height: image height
+ * @rowstride: data rowstride
+ *
+ * Converts the pixel data stored in @src in CAIRO_FORMAT_ARGB32 cairo format
+ * to GDK_COLORSPACE_RGB pixbuf format and move them
+ * to @dst. If @src == @dst, pixel are converted in place.
+ **/
+
+static void
+go_cairo_convert_data_to_pixbuf (unsigned char *dst,
+                                 unsigned char const *src,
+                                 int width,
+                                 int height,
+                                 int rowstride)
+{
+        int i,j;
+        unsigned int t;
+        unsigned char a, b, c;
+
+        g_return_if_fail (dst != NULL);
+
+#define MULT(d,c,a,t) G_STMT_START { t = (a)? c * 255 / a: 0; d = t;} G_STMT_END
+
+        if (src == dst || src == NULL) {
+                for (i = 0; i < height; i++) {
+                        for (j = 0; j < width; j++) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+                                MULT(a, dst[2], dst[3], t);
+                                MULT(b, dst[1], dst[3], t);
+                                MULT(c, dst[0], dst[3], t);
+                                dst[0] = a;
+                                dst[1] = b;
+                                dst[2] = c;
+#else
+                                MULT(a, dst[1], dst[0], t);
+                                MULT(b, dst[2], dst[0], t);
+                                MULT(c, dst[3], dst[0], t);
+                                dst[3] = dst[0];
+                                dst[0] = a;
+                                dst[1] = b;
+                                dst[2] = c;
+#endif
+                                dst += 4;
+                        }
+                        dst += rowstride - width * 4;
+                }
+        } else {
+                for (i = 0; i < height; i++) {
+                        for (j = 0; j < width; j++) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+                                MULT(dst[0], src[2], src[3], t);
+                                MULT(dst[1], src[1], src[3], t);
+                                MULT(dst[2], src[0], src[3], t);
+                                dst[3] = src[3];
+#else
+                                MULT(dst[0], src[1], src[0], t);
+                                MULT(dst[1], src[2], src[0], t);
+                                MULT(dst[2], src[3], src[0], t);
+                                dst[3] = src[0];
+#endif
+                                src += 4;
+                                dst += 4;
+                        }
+                        src += rowstride - width * 4;
+                        dst += rowstride - width * 4;
+                }
+        }
+#undef MULT
+}
+
+static void
+cairo_to_pixbuf (guint8    *src_data,
+                 GdkPixbuf *dst_pixbuf)
+{
+        unsigned char *src;
+        unsigned char *dst;
+        guint          w;
+        guint          h;
+        guint          rowstride;
+
+        w = gdk_pixbuf_get_width (dst_pixbuf);
+        h = gdk_pixbuf_get_height (dst_pixbuf);
+        rowstride = gdk_pixbuf_get_rowstride (dst_pixbuf);
+
+        dst = gdk_pixbuf_get_pixels (dst_pixbuf);
+        src = src_data;
+
+        go_cairo_convert_data_to_pixbuf (dst, src, w, h, rowstride);
+}
+
+static GdkPixbuf *
+frame_pixbuf (GdkPixbuf *source)
+{
+        GdkPixbuf       *dest;
+        cairo_t         *cr;
+        cairo_surface_t *surface;
+        guint            w;
+        guint            h;
+        guint            rowstride;
+        int              frame_width;
+        double           radius;
+        guint8          *data;
+
+        frame_width = 2;
+
+        w = gdk_pixbuf_get_width (source) + frame_width * 2;
+        h = gdk_pixbuf_get_height (source) + frame_width * 2;
+        radius = w / 3.0;
+
+        dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+                               TRUE,
+                               8,
+                               w,
+                               h);
+        rowstride = gdk_pixbuf_get_rowstride (dest);
+
+
+        data = g_new0 (guint8, h * rowstride);
+
+        surface = cairo_image_surface_create_for_data (data,
+                                                       CAIRO_FORMAT_ARGB32,
+                                                       w,
+                                                       h,
+                                                       rowstride);
+        cr = cairo_create (surface);
+        cairo_surface_destroy (surface);
+
+        /* set up image */
+        cairo_rectangle (cr, 0, 0, w, h);
+        cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0);
+        cairo_fill (cr);
+
+        curved_rectangle (cr, frame_width, frame_width,
+                          w - frame_width * 2, h - frame_width * 2,
+                          radius);
+        cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.3);
+        cairo_fill_preserve (cr);
+
+        surface = surface_from_pixbuf (source);
+        cairo_set_source_surface (cr, surface, frame_width, frame_width);
+        cairo_fill (cr);
+        cairo_surface_destroy (surface);
+
+        cairo_to_pixbuf (data, dest);
+
+        cairo_destroy (cr);
+        g_free (data);
+
+        return dest;
+}
+
+GdkPixbuf *
+gdm_user_render_icon (GdmUser   *user,
+                      gint       icon_size)
+{
+        GdkPixbuf    *pixbuf;
+        GdkPixbuf    *framed;
+        char         *path;
+        char         *tmp;
+        gboolean      res;
+
+        g_return_val_if_fail (GDM_IS_USER (user), NULL);
+        g_return_val_if_fail (icon_size > 12, NULL);
+
+        path = NULL;
+
+        pixbuf = render_icon_from_home (user, icon_size);
+        if (pixbuf != NULL) {
+                goto out;
+        }
+
+        /* Try ${GlobalFaceDir}/${username} */
+        path = g_build_filename (GLOBAL_FACEDIR, user->user_name, NULL);
+        res = check_user_file (path,
+                               user->uid,
+                               MAX_FILE_SIZE,
+                               RELAX_GROUP,
+                               RELAX_OTHER);
+        if (res) {
+                pixbuf = gdk_pixbuf_new_from_file_at_size (path,
+                                                           icon_size,
+                                                           icon_size,
+                                                           NULL);
+        } else {
+                pixbuf = NULL;
+        }
+
+        g_free (path);
+        if (pixbuf != NULL) {
+                goto out;
+        }
+
+        /* Finally, ${GlobalFaceDir}/${username}.png */
+        tmp = g_strconcat (user->user_name, ".png", NULL);
+        path = g_build_filename (GLOBAL_FACEDIR, tmp, NULL);
+        g_free (tmp);
+        res = check_user_file (path,
+                               user->uid,
+                               MAX_FILE_SIZE,
+                               RELAX_GROUP,
+                               RELAX_OTHER);
+        if (res) {
+                pixbuf = gdk_pixbuf_new_from_file_at_size (path,
+                                                           icon_size,
+                                                           icon_size,
+                                                           NULL);
+        } else {
+                pixbuf = NULL;
+        }
+ out:
+        g_free (path);
+
+        if (pixbuf != NULL) {
+                framed = frame_pixbuf (pixbuf);
+                if (framed != NULL) {
+                        g_object_unref (pixbuf);
+                        pixbuf = framed;
+                }
+        }
+
+        return pixbuf;
+}

Added: trunk/src/gdmuser/gdm-user.h
==============================================================================
--- (empty file)
+++ trunk/src/gdmuser/gdm-user.h	Wed Feb  4 18:45:38 2009
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2004-2005 James M. Cape <jcape ignore-your tv>.
+ * Copyright (C) 2007-2008 William Jon McCann <mccann jhu edu>
+ *
+ * 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 GdmUserManager
+ */
+
+#ifndef __GDM_USER__
+#define __GDM_USER__ 1
+
+#include <sys/types.h>
+#include <gtk/gtkwidget.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_USER (gdm_user_get_type ())
+#define GDM_USER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDM_TYPE_USER, GdmUser))
+#define GDM_IS_USER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDM_TYPE_USER))
+
+typedef struct _GdmUser GdmUser;
+
+GType                 gdm_user_get_type            (void) G_GNUC_CONST;
+
+uid_t                 gdm_user_get_uid             (GdmUser   *user);
+G_CONST_RETURN char  *gdm_user_get_user_name       (GdmUser   *user);
+G_CONST_RETURN char  *gdm_user_get_real_name       (GdmUser   *user);
+G_CONST_RETURN char  *gdm_user_get_home_directory  (GdmUser   *user);
+G_CONST_RETURN char  *gdm_user_get_shell           (GdmUser   *user);
+guint                 gdm_user_get_num_sessions    (GdmUser   *user);
+GList                *gdm_user_get_sessions        (GdmUser   *user);
+gulong                gdm_user_get_login_frequency (GdmUser   *user);
+
+GdkPixbuf            *gdm_user_render_icon         (GdmUser   *user,
+                                                    gint       icon_size);
+
+gint                  gdm_user_collate             (GdmUser   *user1,
+                                                    GdmUser   *user2);
+
+G_END_DECLS
+
+#endif

Added: trunk/src/shell-status-menu.c
==============================================================================
--- (empty file)
+++ trunk/src/shell-status-menu.c	Wed Feb  4 18:45:38 2009
@@ -0,0 +1,632 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* Adapted from gdm/gui/user-switch-applet/applet.c */
+/*
+ *
+ * Copyright (C) 2004-2005 James M. Cape <jcape ignore-your tv>.
+ * Copyright (C) 2008,2009      Red Hat, Inc.
+ *
+ * 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 "shell-status-menu.h"
+
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include <gconf/gconf.h>
+#include <gconf/gconf-client.h>
+
+#include <dbus/dbus-glib.h>
+
+#define GDMUSER_I_KNOW_THIS_IS_UNSTABLE
+#include <gdmuser/gdm-user-manager.h>
+
+#include "shell-global.h"
+
+#define LOCKDOWN_DIR    "/desktop/gnome/lockdown"
+#define LOCKDOWN_KEY    LOCKDOWN_DIR "/disable_user_switching"
+
+struct _ShellStatusMenuPrivate {
+  GConfClient    *client;
+  GdmUserManager *manager;
+  GdmUser        *user;
+
+  ClutterTexture *user_icon;
+  BigBox         *name_box;
+  ClutterLabel   *name;
+
+  GtkWidget      *menu;
+  GtkWidget      *account_item;
+  GtkWidget      *control_panel_item;
+  GtkWidget      *lock_screen_item;
+  GtkWidget      *login_screen_item;
+  GtkWidget      *quit_session_item;
+
+  guint           client_notify_lockdown_id;
+
+  guint           current_status_state;
+
+  guint           user_icon_changed_id;
+  guint           user_notify_id;
+
+  gboolean        has_other_users;
+
+  GtkIconSize     icon_size;
+  guint           pixel_size;
+};
+
+enum {
+  PROP_0
+};
+
+G_DEFINE_TYPE(ShellStatusMenu, shell_status_menu, BIG_TYPE_BOX);
+
+/* Signals */
+enum
+{
+  DEACTIVATED,
+  LAST_SIGNAL
+};
+
+static guint shell_status_menu_signals [LAST_SIGNAL] = { 0 };
+
+static void
+reset_icon (ShellStatusMenu *status)
+{
+  ShellStatusMenuPrivate *priv = status->priv;
+  GdkPixbuf *pixbuf;
+
+  if (priv->user == NULL)
+    return;
+
+  if (priv->user_icon != NULL)
+    {
+      pixbuf = gdm_user_render_icon (priv->user, 24);
+
+      if (pixbuf == NULL)
+        return;
+
+      shell_clutter_texture_set_from_pixbuf (priv->user_icon, pixbuf);
+
+      g_object_unref (pixbuf);
+    }
+}
+
+static void
+update_label (ShellStatusMenu *status)
+{
+  ShellStatusMenuPrivate *priv = status->priv;
+  char      *markup;
+
+  markup = g_strdup_printf ("<b>%s</b>",
+                            gdm_user_get_real_name (GDM_USER (priv->user)));
+  clutter_label_set_use_markup (priv->name, TRUE);
+  clutter_label_set_text (priv->name, markup);
+  g_free (markup);
+}
+
+static void
+on_user_icon_changed (GdmUser         *user,
+                      ShellStatusMenu *status)
+{
+  g_debug ("User icon changed");
+  reset_icon (status);
+}
+
+static void
+user_notify_display_name_cb (GObject       *object,
+                             GParamSpec    *pspec,
+                             ShellStatusMenu *status)
+{
+  update_label (status);
+}
+
+static void
+setup_current_user (ShellStatusMenu *status)
+{
+  ShellStatusMenuPrivate *priv = status->priv;
+  const char *name;
+
+  priv->user = gdm_user_manager_get_user_by_uid (priv->manager, getuid ());
+  if (priv->user != NULL)
+    {
+      g_object_ref (priv->user);
+      name = gdm_user_get_real_name (priv->user);
+    }
+  else
+    {
+      name = _("Unknown");
+    }
+/*
+  priv->menuitem = gtk_image_menu_item_new_with_label (name);
+        label = GTK_BIN (adata->menuitem)->child;
+        gtk_menu_shell_append (GTK_MENU_SHELL (adata->menubar), adata->menuitem);
+        gtk_widget_show (adata->menuitem);
+*/
+
+  update_label (status);
+
+  if (priv->user != NULL)
+    {
+      reset_icon (status);
+
+      priv->user_icon_changed_id =
+        g_signal_connect (priv->user,
+                          "icon-changed",
+                          G_CALLBACK (on_user_icon_changed),
+                          status);
+      priv->user_notify_id =
+        g_signal_connect (priv->user,
+                          "notify::display-name",
+                          G_CALLBACK (user_notify_display_name_cb),
+                          status);
+    }
+}
+
+static void
+maybe_lock_screen (ShellStatusMenu *status)
+{
+  char *args[3];
+  GError *err;
+  GdkScreen *screen;
+  gboolean use_gscreensaver = TRUE;
+  gboolean res;
+
+  g_debug ("Attempting to lock screen");
+
+  args[0] = g_find_program_in_path ("gnome-screensaver-command");
+  if (args[0] == NULL)
+    {
+      args[0] = g_find_program_in_path ("xscreensaver-command");
+      use_gscreensaver = FALSE;
+    }
+
+  if (args[0] == NULL)
+    return;
+
+  if (use_gscreensaver)
+    args[1] = "--lock";
+  else
+    args[1] = "-lock";
+  args[2] = NULL;
+
+  screen = gdk_screen_get_default ();
+
+  err = NULL;
+  res = gdk_spawn_on_screen (screen, g_get_home_dir (), args, NULL, 0, NULL,
+      NULL, NULL, &err);
+  if (!res)
+    {
+      g_warning (_("Can't lock screen: %s"), err->message);
+      g_error_free (err);
+    }
+
+  if (use_gscreensaver)
+    args[1] = "--throttle";
+  else
+    args[1] = "-throttle";
+
+  err = NULL;
+  res = 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);
+  if (!res)
+    {
+      g_warning (_("Can't temporarily set screensaver to blank screen: %s"),
+          err->message);
+      g_error_free (err);
+    }
+
+  g_free (args[0]);
+}
+
+static void
+on_lock_screen_activate (GtkMenuItem   *item,
+                         ShellStatusMenu *status)
+{
+  maybe_lock_screen (status);
+}
+
+static void
+do_switch (ShellStatusMenu *status,
+           GdmUser       *user)
+{
+  ShellStatusMenuPrivate *priv = status->priv;
+  guint num_sessions;
+
+  g_debug ("Do user switch");
+
+  if (user == NULL)
+    {
+      gdm_user_manager_goto_login_session (priv->manager);
+      goto out;
+    }
+
+  num_sessions = gdm_user_get_num_sessions (user);
+  if (num_sessions > 0)
+    gdm_user_manager_activate_user_session (priv->manager, user);
+  else
+    gdm_user_manager_goto_login_session (priv->manager);
+out:
+  maybe_lock_screen (status);
+}
+
+static void
+on_login_screen_activate (GtkMenuItem   *item,
+                          ShellStatusMenu *status)
+{
+  GdmUser *user;
+
+  user = NULL;
+
+  do_switch (status, user);
+}
+
+static void
+spawn_external (ShellStatusMenu *status, const char *program)
+{
+  char *args[2];
+  GError *error;
+  GdkScreen *screen;
+  gboolean res;
+
+  args[0] = g_find_program_in_path (program);
+  if (args[0] == NULL)
+    return;
+  args[1] = NULL;
+
+  screen = gdk_screen_get_default ();
+
+  error = NULL;
+  res = gdk_spawn_on_screen (screen, g_get_home_dir (), args, NULL, 0, NULL,
+      NULL, NULL, &error);
+  if (!res)
+    {
+      g_warning ("Failed to exec %s: %s", program, error->message);
+      g_clear_error (&error);
+    }
+
+  g_free (args[0]);
+
+}
+
+static void
+on_control_panel_activate (GtkMenuItem   *item,
+                           ShellStatusMenu *status)
+{
+  spawn_external (status, "gnome-control-center");
+}
+
+static void
+on_account_activate (GtkMenuItem   *item,
+                     ShellStatusMenu *status)
+{
+  spawn_external (status, "gnome-about-me");
+}
+
+
+static void
+on_quit_session_activate (GtkMenuItem   *item,
+                          ShellStatusMenu *status)
+{
+  char      *args[3];
+  GError    *error;
+  GdkScreen *screen;
+  gboolean   res;
+
+  args[0] = g_find_program_in_path ("gnome-session-save");
+  if (args[0] == NULL)
+    return;
+
+  args[1] = "--logout-dialog";
+  args[2] = NULL;
+
+  screen = gdk_screen_get_default ();
+
+  error = NULL;
+  res = gdk_spawn_on_screen (screen, g_get_home_dir (), args, NULL, 0, NULL,
+      NULL, NULL, &error);
+  if (!res)
+    {
+      g_warning (_("Can't logout: %s"), error->message);
+      g_error_free (error);
+    }
+
+  g_free (args[0]);
+}
+
+static void
+update_switch_user (ShellStatusMenu *status)
+{
+  ShellStatusMenuPrivate *priv = status->priv;
+  GSList *users;
+
+  users = gdm_user_manager_list_users (priv->manager);
+  priv->has_other_users = FALSE;
+  if (users != NULL)
+    priv->has_other_users = g_slist_length (users) > 1;
+  g_slist_free (users);
+
+  if (priv->has_other_users)
+    gtk_widget_show (priv->login_screen_item);
+  else
+    gtk_widget_hide (priv->login_screen_item);
+}
+
+static void
+on_manager_user_added (GdmUserManager *manager,
+                       GdmUser        *user,
+                       ShellStatusMenu *status)
+{
+  update_switch_user (status);
+}
+
+static void
+on_manager_user_removed (GdmUserManager *manager,
+                         GdmUser        *user,
+                         ShellStatusMenu *status)
+{
+  update_switch_user (status);
+}
+
+static void
+on_manager_users_loaded (GdmUserManager *manager,
+                         ShellStatusMenu *status)
+{
+  update_switch_user (status);
+}
+
+static void
+menu_style_set_cb (GtkWidget *menu, GtkStyle *old_style,
+                   ShellStatusMenu *status)
+{
+  ShellStatusMenuPrivate *priv = status->priv;
+  GtkSettings *settings;
+  int width;
+  int height;
+
+  priv->icon_size = gtk_icon_size_from_name ("panel-menu");
+  if (priv->icon_size == GTK_ICON_SIZE_INVALID)
+    priv->icon_size = gtk_icon_size_register ("panel-menu", 24, 24);
+
+  if (gtk_widget_has_screen (menu))
+    settings = gtk_settings_get_for_screen (gtk_widget_get_screen (menu));
+  else
+    settings = gtk_settings_get_default ();
+
+  if (!gtk_icon_size_lookup_for_settings (settings, priv->icon_size, &width,
+      &height))
+    priv->pixel_size = -1;
+  else
+    priv->pixel_size = MAX(width, height);
+}
+
+static void
+menuitem_style_set_cb (GtkWidget     *menuitem,
+                       GtkStyle      *old_style,
+                       ShellStatusMenu *status)
+{
+  GtkWidget *image;
+  const char *icon_name;
+  ShellStatusMenuPrivate *priv = status->priv;
+
+  if (menuitem == priv->login_screen_item)
+    icon_name = "system-users";
+  else if (menuitem == priv->lock_screen_item)
+    icon_name = "system-lock-screen";
+  else if (menuitem == priv->quit_session_item)
+    icon_name = "system-log-out";
+  else if (menuitem == priv->account_item)
+    icon_name = "user-info";
+  else if (menuitem == priv->control_panel_item)
+    icon_name = "preferences-desktop";
+  else
+    icon_name = GTK_STOCK_MISSING_IMAGE;
+
+  image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (menuitem));
+  gtk_image_set_pixel_size (GTK_IMAGE (image), priv->pixel_size);
+  gtk_image_set_from_icon_name (GTK_IMAGE (image), icon_name, priv->icon_size);
+}
+
+static void
+on_deactivate (GtkMenuShell *menushell, gpointer user_data)
+{
+  ShellStatusMenu *shell = SHELL_STATUS_MENU (user_data);
+  g_signal_emit (G_OBJECT (shell), shell_status_menu_signals[DEACTIVATED], 0);
+}
+
+static void
+create_sub_menu (ShellStatusMenu *status)
+{
+  ShellStatusMenuPrivate *priv = status->priv;
+  GtkWidget *item;
+
+  priv->menu = gtk_menu_new ();
+  g_signal_connect (priv->menu, "style-set", G_CALLBACK (menu_style_set_cb),
+      status);
+
+  g_signal_connect (priv->manager, "users-loaded",
+      G_CALLBACK (on_manager_users_loaded), status);
+  g_signal_connect (priv->manager, "user-added",
+      G_CALLBACK (on_manager_user_added), status);
+  g_signal_connect (priv->manager, "user-removed",
+      G_CALLBACK (on_manager_user_removed), status);
+
+  priv->account_item = gtk_image_menu_item_new_with_label (_("Account Information..."));
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (priv->account_item),
+      gtk_image_new ());
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), priv->account_item);
+  g_signal_connect (priv->account_item, "style-set",
+      G_CALLBACK (menuitem_style_set_cb), status);
+  g_signal_connect (priv->account_item, "activate",
+      G_CALLBACK (on_account_activate), status);
+  gtk_widget_show (priv->account_item);
+
+  priv->control_panel_item = gtk_image_menu_item_new_with_label (_(
+      "System Preferences..."));
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (priv->control_panel_item),
+      gtk_image_new ());
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), priv->control_panel_item);
+  g_signal_connect (priv->control_panel_item, "style-set",
+      G_CALLBACK (menuitem_style_set_cb), status);
+  g_signal_connect (priv->control_panel_item, "activate",
+      G_CALLBACK (on_control_panel_activate), status);
+  gtk_widget_show (priv->control_panel_item);
+
+  item = gtk_separator_menu_item_new ();
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), item);
+  gtk_widget_show (item);
+
+  priv->lock_screen_item
+      = gtk_image_menu_item_new_with_label (_("Lock Screen"));
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (priv->lock_screen_item),
+      gtk_image_new ());
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), priv->lock_screen_item);
+  g_signal_connect (priv->lock_screen_item, "style-set",
+      G_CALLBACK (menuitem_style_set_cb), status);
+  g_signal_connect (priv->lock_screen_item, "activate",
+      G_CALLBACK (on_lock_screen_activate), status);
+  gtk_widget_show (priv->lock_screen_item);
+
+  priv->login_screen_item = gtk_image_menu_item_new_with_label (_("Switch User"));
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (priv->login_screen_item),
+      gtk_image_new ());
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), priv->login_screen_item);
+  g_signal_connect (priv->login_screen_item, "style-set",
+      G_CALLBACK (menuitem_style_set_cb), status);
+  g_signal_connect (priv->login_screen_item, "activate",
+      G_CALLBACK (on_login_screen_activate), status);
+  /* Only show switch user if there are other users */
+
+  priv->quit_session_item = gtk_image_menu_item_new_with_label (_("Quit..."));
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (priv->quit_session_item),
+      gtk_image_new ());
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), priv->quit_session_item);
+  g_signal_connect (priv->quit_session_item, "style-set",
+      G_CALLBACK (menuitem_style_set_cb), status);
+  g_signal_connect (priv->quit_session_item, "activate",
+      G_CALLBACK (on_quit_session_activate), status);
+  gtk_widget_show (priv->quit_session_item);
+
+  g_signal_connect (G_OBJECT (priv->menu), "deactivate",
+      G_CALLBACK (on_deactivate), status);
+}
+
+static void
+shell_status_menu_init (ShellStatusMenu *status)
+{
+  ShellStatusMenuPrivate *priv;
+
+  status->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (status, SHELL_TYPE_STATUS_MENU,
+                                                     ShellStatusMenuPrivate);
+
+  g_object_set (G_OBJECT (status),
+                "orientation", BIG_BOX_ORIENTATION_HORIZONTAL,
+                NULL);
+  priv->client = gconf_client_get_default ();
+
+  priv->user_icon = CLUTTER_TEXTURE (clutter_texture_new ());
+  big_box_append (BIG_BOX (status), CLUTTER_ACTOR (status->priv->user_icon), 0);
+
+  priv->name_box = BIG_BOX (big_box_new (BIG_BOX_ORIENTATION_VERTICAL));
+  g_object_set (G_OBJECT (priv->name_box), "y-align", BIG_BOX_ALIGNMENT_CENTER, NULL);
+  big_box_append (BIG_BOX (status), CLUTTER_ACTOR (priv->name_box), BIG_BOX_PACK_EXPAND);
+  priv->name = CLUTTER_LABEL (clutter_label_new ());
+  big_box_append (BIG_BOX (priv->name_box), CLUTTER_ACTOR (priv->name), BIG_BOX_PACK_EXPAND);
+
+  priv->manager = gdm_user_manager_ref_default ();
+  setup_current_user (status);
+
+  create_sub_menu (status);
+}
+
+static void
+shell_status_menu_finalize (GObject *object)
+{
+  ShellStatusMenu *status = SHELL_STATUS_MENU (object);
+  ShellStatusMenuPrivate *priv = status->priv;
+
+  gconf_client_notify_remove (priv->client, priv->client_notify_lockdown_id);
+
+  g_signal_handler_disconnect (priv->user, priv->user_notify_id);
+  g_signal_handler_disconnect (priv->user, priv->user_icon_changed_id);
+
+  if (priv->user != NULL) {
+    g_object_unref (priv->user);
+  }
+  g_object_unref (priv->client);
+  g_object_unref (priv->manager);
+
+  G_OBJECT_CLASS (shell_status_menu_parent_class)->finalize (object);
+}
+
+static void
+shell_status_menu_class_init (ShellStatusMenuClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (ShellStatusMenuPrivate));
+
+  gobject_class->finalize = shell_status_menu_finalize;
+
+  shell_status_menu_signals[DEACTIVATED] =
+    g_signal_new ("deactivated",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (ShellStatusMenuClass, deactivated),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+}
+
+ShellStatusMenu *
+shell_status_menu_new (void)
+{
+  return g_object_new (SHELL_TYPE_STATUS_MENU, NULL);
+}
+
+static void
+position_menu (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data)
+{
+  ShellStatusMenu *status = SHELL_STATUS_MENU (user_data);
+  int src_x, src_y;
+
+  clutter_actor_get_transformed_position (CLUTTER_ACTOR (status), &src_x, &src_y);
+
+  *x = src_x;
+  *y = src_y;
+}
+
+void
+shell_status_menu_toggle (ShellStatusMenu *status, ClutterEvent *event)
+{
+  ShellStatusMenuPrivate *priv = status->priv;
+
+  if (GTK_WIDGET_VISIBLE (GTK_WIDGET (priv->menu)))
+    {
+      gtk_widget_hide (GTK_WIDGET (priv->menu));
+    }
+  else
+    {
+      gtk_widget_show (GTK_WIDGET (priv->menu));
+      gtk_menu_popup (GTK_MENU (priv->menu), NULL, NULL, position_menu,
+          status, 1, event->button.time);
+    }
+}
\ No newline at end of file

Added: trunk/src/shell-status-menu.h
==============================================================================
--- (empty file)
+++ trunk/src/shell-status-menu.h	Wed Feb  4 18:45:38 2009
@@ -0,0 +1,42 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#ifndef __SHELL_STATUS_MENU_H__
+#define __SHELL_STATUS_MENU_H__
+
+#include <clutter/clutter.h>
+#include "big/box.h"
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_STATUS_MENU			(shell_status_menu_get_type ())
+#define SHELL_STATUS_MENU(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_STATUS_MENU, ShellStatusMenu))
+#define SHELL_STATUS_MENU_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_STATUS_MENU, ShellStatusMenuClass))
+#define SHELL_IS_STATUS_MENU(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_STATUS_MENU))
+#define SHELL_IS_STATUS_MENU_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_STATUS_MENU))
+#define SHELL_STATUS_MENU_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_STATUS_MENU, ShellStatusMenuClass))
+
+typedef struct _ShellStatusMenu        ShellStatusMenu;
+typedef struct _ShellStatusMenuPrivate ShellStatusMenuPrivate;
+typedef struct _ShellStatusMenuClass   ShellStatusMenuClass;
+
+struct _ShellStatusMenu
+{
+  BigBox parent_instance;
+
+  ShellStatusMenuPrivate *priv;
+};
+
+struct _ShellStatusMenuClass
+{
+  BigBoxClass parent_class;
+
+  void (*deactivated) (ShellStatusMenu *status, gpointer user_data);
+};
+
+GType             shell_status_menu_get_type     (void);
+
+void              shell_status_menu_toggle       (ShellStatusMenu *menu, ClutterEvent *event);
+
+G_END_DECLS
+
+#endif /* __SHELL_STATUS_MENU_H__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]