[gdm/gnome-3-8] Revert "gui: drop fallback greeter"



commit 1732d1c22e88d6f05f5b5e234990ce68b0e59cc0
Author: Ray Strode <rstrode redhat com>
Date:   Mon Jun 17 07:54:40 2013 -0400

    Revert "gui: drop fallback greeter"
    
    This reverts commit 8dbec8431d558b9d33adc987de2f7a58815589d4.

 configure.ac                                       |   31 +
 data/Makefile.am                                   |   13 +-
 docs/C/index.docbook                               |    4 +-
 gui/Makefile.am                                    |    2 +
 gui/simple-greeter/Makefile.am                     |  258 ++
 gui/simple-greeter/extensions/Makefile.am          |    5 +
 .../extensions/fingerprint/Makefile.am             |   46 +
 .../fingerprint/gdm-fingerprint-extension.c        |  464 ++++
 .../fingerprint/gdm-fingerprint-extension.h        |   56 +
 .../extensions/fingerprint/icons/16x16/Makefile.am |    5 +
 .../fingerprint/icons/16x16/gdm-fingerprint.png    |  Bin 0 -> 461 bytes
 .../extensions/fingerprint/icons/48x48/Makefile.am |    5 +
 .../fingerprint/icons/48x48/gdm-fingerprint.png    |  Bin 0 -> 1638 bytes
 .../extensions/fingerprint/icons/Makefile.am       |    1 +
 gui/simple-greeter/extensions/fingerprint/page.ui  |   57 +
 gui/simple-greeter/extensions/password/Makefile.am |   43 +
 .../extensions/password/gdm-password-extension.c   |  446 +++
 .../extensions/password/gdm-password-extension.h   |   56 +
 gui/simple-greeter/extensions/password/page.ui     |   57 +
 .../extensions/smartcard/Makefile.am               |   66 +
 .../extensions/smartcard/gdm-smartcard             |   18 +
 .../extensions/smartcard/gdm-smartcard-extension.c |  604 +++++
 .../extensions/smartcard/gdm-smartcard-extension.h |   56 +
 .../extensions/smartcard/gdm-smartcard-manager.c   | 1445 ++++++++++
 .../extensions/smartcard/gdm-smartcard-manager.h   |   86 +
 .../extensions/smartcard/gdm-smartcard-worker.c    |  184 ++
 .../extensions/smartcard/gdm-smartcard.c           |  552 ++++
 .../extensions/smartcard/gdm-smartcard.h           |   94 +
 .../extensions/smartcard/icons/16x16/Makefile.am   |    5 +
 .../smartcard/icons/16x16/gdm-smartcard.png        |  Bin 0 -> 871 bytes
 .../extensions/smartcard/icons/48x48/Makefile.am   |    5 +
 .../smartcard/icons/48x48/gdm-smartcard.png        |  Bin 0 -> 4202 bytes
 .../extensions/smartcard/icons/Makefile.am         |    1 +
 gui/simple-greeter/extensions/smartcard/page.ui    |   57 +
 gui/simple-greeter/extensions/unified/Makefile.am  |   42 +
 .../extensions/unified/gdm-unified-extension.c     |  441 +++
 .../extensions/unified/gdm-unified-extension.h     |   57 +
 gui/simple-greeter/extensions/unified/gdm.pam      |   12 +
 gui/simple-greeter/extensions/unified/page.ui      |   57 +
 gui/simple-greeter/gdm-cell-renderer-timer.c       |  259 ++
 gui/simple-greeter/gdm-cell-renderer-timer.h       |   57 +
 gui/simple-greeter/gdm-chooser-widget.c            | 2849 ++++++++++++++++++++
 gui/simple-greeter/gdm-chooser-widget.h            |  151 ++
 gui/simple-greeter/gdm-clock-widget.c              |  314 +++
 gui/simple-greeter/gdm-clock-widget.h              |   56 +
 gui/simple-greeter/gdm-extension-list.c            |  388 +++
 gui/simple-greeter/gdm-extension-list.h            |   70 +
 gui/simple-greeter/gdm-greeter-login-window.c      | 2623 ++++++++++++++++++
 gui/simple-greeter/gdm-greeter-login-window.h      |  106 +
 gui/simple-greeter/gdm-greeter-login-window.ui     |  284 ++
 gui/simple-greeter/gdm-greeter-panel.c             | 1207 +++++++++
 gui/simple-greeter/gdm-greeter-panel.h             |   72 +
 gui/simple-greeter/gdm-greeter-session.c           |  698 +++++
 gui/simple-greeter/gdm-greeter-session.h           |   58 +
 gui/simple-greeter/gdm-option-widget.c             | 1144 ++++++++
 gui/simple-greeter/gdm-option-widget.h             |   87 +
 gui/simple-greeter/gdm-remote-login-window.c       |  314 +++
 gui/simple-greeter/gdm-remote-login-window.h       |   61 +
 gui/simple-greeter/gdm-scrollable-widget.c         |  909 +++++++
 gui/simple-greeter/gdm-scrollable-widget.h         |   74 +
 gui/simple-greeter/gdm-session-option-widget.c     |  192 ++
 gui/simple-greeter/gdm-session-option-widget.h     |   62 +
 gui/simple-greeter/gdm-sessions.c                  |  265 ++
 gui/simple-greeter/gdm-sessions.h                  |   38 +
 gui/simple-greeter/gdm-timer.c                     |  327 +++
 gui/simple-greeter/gdm-timer.h                     |   62 +
 gui/simple-greeter/gdm-user-chooser-dialog.c       |  199 ++
 gui/simple-greeter/gdm-user-chooser-dialog.h       |   62 +
 gui/simple-greeter/gdm-user-chooser-widget.c       | 1366 ++++++++++
 gui/simple-greeter/gdm-user-chooser-widget.h       |   70 +
 gui/simple-greeter/gdm-user-private.h              |   49 +
 gui/simple-greeter/greeter-main.c                  |  284 ++
 gui/simple-greeter/libgdmsimplegreeter/Makefile.am |   43 +
 .../libgdmsimplegreeter/gdm-login-extension.c      |  277 ++
 .../libgdmsimplegreeter/gdm-login-extension.h      |  128 +
 .../libgdmsimplegreeter/gdmsimplegreeter.pc.in     |   11 +
 .../org.gnome.SessionManager.ClientPrivate.xml     |  123 +
 gui/simple-greeter/org.gnome.SessionManager.xml    |  392 +++
 gui/simple-greeter/test-a11y-preferences.c         |   56 +
 gui/simple-greeter/test-filesystem-type.c          |  107 +
 gui/simple-greeter/test-greeter-background.c       |   54 +
 gui/simple-greeter/test-greeter-login-window.c     |  121 +
 gui/simple-greeter/test-greeter-panel.c            |   63 +
 gui/simple-greeter/test-languages.c                |   81 +
 gui/simple-greeter/test-remote-login-window.c      |  100 +
 gui/simple-greeter/test-sessions.c                 |   81 +
 gui/simple-greeter/test-user-chooser.c             |   69 +
 gui/simple-greeter/test-user-manager.c             |  148 +
 88 files changed, 21937 insertions(+), 5 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index d9e89a9..42c186c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -150,6 +150,16 @@ fi
 AC_SUBST(LIBSELINUX_CFLAGS)
 AC_SUBST(LIBSELINUX_LIBS)
 
+PKG_CHECK_MODULES(SIMPLE_GREETER,
+        gtk+-3.0 >= $GTK_REQUIRED_VERSION
+       fontconfig >= $FONTCONFIG_REQUIRED_VERSION
+        accountsservice >= $ACCOUNTS_SERVICE_REQUIRED_VERSION
+       x11
+)
+SIMPLE_GREETER_LIBS="$SIMPLE_GREETER_LIBS -lm"
+AC_SUBST(SIMPLE_GREETER_CFLAGS)
+AC_SUBST(SIMPLE_GREETER_LIBS)
+
 PKG_CHECK_MODULES(SIMPLE_CHOOSER,
         gtk+-3.0 >= $GTK_REQUIRED_VERSION
 )
@@ -281,6 +291,11 @@ AC_ARG_WITH(plymouth,
             AS_HELP_STRING([--with-plymouth],
                            [Add plymouth support @<:@default=auto@:>@]),
             [with_plymouth=$withval], [with_plymouth=auto])
+AC_ARG_ENABLE(fallback-greeter,
+             AS_HELP_STRING([--enable-fallback-greeter],
+                             [Enable fallback greeter @<:@default=no@:>@]),,
+              enable_fallback_greeter=no)
+AM_CONDITIONAL(ENABLE_FALLBACK_GREETER, test x$enable_fallback_greeter = xyes)
 
 AC_ARG_WITH(at-spi-registryd-directory,
             AS_HELP_STRING([--with-at-spi-registryd-directory],
@@ -353,6 +368,7 @@ EXTRA_FLEXI_LIBS=""
 EXTRA_DYNAMIC_LIBS=""
 EXTRA_SETUP_LIBS=""
 EXTRA_TEST_LIBS=""
+EXTRA_GREETER_LIBS=""
 
 AC_CHECK_FUNC(socket,,[
              AC_CHECK_LIB(socket,socket, [
@@ -1215,6 +1231,7 @@ AC_SUBST(EXTRA_FLEXI_LIBS)
 AC_SUBST(EXTRA_DYNAMIC_LIBS)
 AC_SUBST(EXTRA_SETUP_LIBS)
 AC_SUBST(EXTRA_TEST_LIBS)
+AC_SUBST(EXTRA_GREETER_LIBS)
 
 # Check for Solaris logindevperm support
 #
@@ -1574,6 +1591,20 @@ docs/Makefile
 gui/Makefile
 gui/libgdm/Makefile
 gui/libgdm/gdm.pc
+gui/simple-greeter/Makefile
+gui/simple-greeter/libgdmsimplegreeter/Makefile
+gui/simple-greeter/libgdmsimplegreeter/gdmsimplegreeter.pc
+gui/simple-greeter/extensions/Makefile
+gui/simple-greeter/extensions/unified/Makefile
+gui/simple-greeter/extensions/password/Makefile
+gui/simple-greeter/extensions/fingerprint/Makefile
+gui/simple-greeter/extensions/fingerprint/icons/Makefile
+gui/simple-greeter/extensions/fingerprint/icons/16x16/Makefile
+gui/simple-greeter/extensions/fingerprint/icons/48x48/Makefile
+gui/simple-greeter/extensions/smartcard/Makefile
+gui/simple-greeter/extensions/smartcard/icons/Makefile
+gui/simple-greeter/extensions/smartcard/icons/16x16/Makefile
+gui/simple-greeter/extensions/smartcard/icons/48x48/Makefile
 gui/simple-chooser/Makefile
 utils/Makefile
 data/gdm.conf
diff --git a/data/Makefile.am b/data/Makefile.am
index 81eb8ef..3d7bf3b 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -80,9 +80,15 @@ gdm.schemas.in: $(srcdir)/gdm.schemas.in.in
                -e 's,[ ]sbindir[@],$(sbindir),g' \
                <$(srcdir)/gdm.schemas.in.in >gdm.schemas.in
 
-EXTRA_DIST += gdm-shell.session.in
+if ENABLE_FALLBACK_GREETER
+FALLBACK_SESSION_IN = $(srcdir)/gdm-shell-with-fallback.session.in
+else
+FALLBACK_SESSION_IN = $(srcdir)/gdm-shell.session.in
+endif
+
+EXTRA_DIST += gdm-shell.session.in gdm-shell-with-fallback.session.in
 
-gdm-shell.session: $(srcdir)/gdm-shell.session.in
+gdm-shell.session: $(FALLBACK_SESSION_IN)
        sed     -e 's,[ ]libexecdir[@],$(libexecdir),g' \
                -e 's,[ ]CHECK_ACCELERATED_DIR[@],$(CHECK_ACCELERATED_DIR),g' \
                < $< > $  tmp && mv $  tmp $@
@@ -91,7 +97,7 @@ localealiasdir = $(datadir)/gdm
 localealias_DATA = locale.alias
 
 sessiondir = $(datadir)/gnome-session/sessions
-session_DATA =  gdm-shell.session
+session_DATA = gdm-fallback.session gdm-shell.session
 
 pam_redhat_files = pam-redhat/gdm.pam          \
        pam-redhat/gdm-autologin.pam            \
@@ -150,6 +156,7 @@ EXTRA_DIST +=                       \
        gdm.schemas.in.in       \
        gdm.conf-custom.in      \
        Xsession.in             \
+       gdm-fallback.session    \
        Init.in                 \
        PreSession.in           \
        PostSession.in          \
diff --git a/docs/C/index.docbook b/docs/C/index.docbook
index fe6976e..1def3b4 100644
--- a/docs/C/index.docbook
+++ b/docs/C/index.docbook
@@ -116,7 +116,7 @@
     </para>
 
     <para>
-      Greeter - The graphical login window (provided by <command>gnome-shell</command>).
+      Greeter - The graphical login window (<command>gdm-simple-greeter</command>).
     </para>
 
     <para>
@@ -385,7 +385,7 @@
       <para>
         The Face Browser is the interface which allows users to select their
         username by clicking on an image.  This feature can be enabled or
-        disabled via the org.gnome.login-screen disable-user-list GSettings 
+        disabled via the /apps/gdm/simple-greeter/disable_user_list GConf
         key and is on by default.  When disabled, users must type their
         complete username by hand.  When enabled, it displays all local users
         which are available for login on the system (all user accounts defined
diff --git a/gui/Makefile.am b/gui/Makefile.am
index 04da105..ce90055 100644
--- a/gui/Makefile.am
+++ b/gui/Makefile.am
@@ -2,6 +2,7 @@ NULL =
 
 SUBDIRS =                      \
        libgdm                  \
+       simple-greeter          \
        $(NULL)
 
 if XDMCP_SUPPORT
@@ -11,4 +12,5 @@ endif
 DIST_SUBDIRS =                 \
        libgdm                  \
        simple-chooser          \
+       simple-greeter          \
        $(NULL)
diff --git a/gui/simple-greeter/Makefile.am b/gui/simple-greeter/Makefile.am
new file mode 100644
index 0000000..138114b
--- /dev/null
+++ b/gui/simple-greeter/Makefile.am
@@ -0,0 +1,258 @@
+NULL =
+SUBDIRS =                              \
+       libgdmsimplegreeter             \
+       extensions                              \
+       $(NULL)
+
+AM_CPPFLAGS = \
+       -I$(top_srcdir)/common                          \
+       -I$(top_builddir)/common                        \
+       -I$(top_srcdir)/gui/libgdm                      \
+       -I$(top_builddir)/gui/libgdm                    \
+       -I$(top_srcdir)/gui/simple-greeter/libgdmsimplegreeter  \
+       -DDMCONFDIR=\""$(dmconfdir)"\"                  \
+       -DGDMCONFDIR=\"$(gdmconfdir)\"                  \
+       -DDATADIR=\""$(datadir)"\"                      \
+       -DSYSCONFDIR=\""$(sysconfdir)"\"                \
+       -DLIBLOCALEDIR=\""$(prefix)/lib/locale"\"       \
+       -DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
+       -DUIDIR=\""$(pkgdatadir)"\"                     \
+       -DLIBEXECDIR=\""$(libexecdir)"\"                \
+       -DSBINDIR=\""$(sbindir)"\"                      \
+       -DGDM_CACHE_DIR=\""$(localstatedir)/cache/gdm"\"        \
+       -DAT_SPI_REGISTRYD_DIR="\"$(AT_SPI_REGISTRYD_DIR)\""    \
+       $(UPOWER_CFLAGS)                                \
+       -DGDM_SIMPLE_GREETER_PLUGINS_DIR="\"$(GDM_SIMPLE_GREETER_PLUGINS_DIR)\""\
+       $(DISABLE_DEPRECATED_CFLAGS)                    \
+       $(GTK_CFLAGS)                                   \
+       $(SIMPLE_GREETER_CFLAGS)                        \
+       $(NULL)
+
+noinst_PROGRAMS =                      \
+       test-filesystem-type            \
+       test-greeter-login-window       \
+       test-greeter-panel              \
+       test-sessions                   \
+       test-remote-login-window        \
+       test-user-chooser               \
+       test-user-manager               \
+       $(NULL)
+
+gsm-client-glue.c gsm-client-glue.h : org.gnome.SessionManager.ClientPrivate.xml Makefile.am
+       $(AM_V_GEN)gdbus-codegen                                        \
+               --c-namespace Gsm                               \
+               --interface-prefix=org.gnome.SessionManager             \
+               --generate-c-code=gsm-client-glue                       \
+               $(srcdir)/org.gnome.SessionManager.ClientPrivate.xml
+
+gsm-manager-glue.c gsm-manager-glue.h : org.gnome.SessionManager.xml Makefile.am
+       $(AM_V_GEN)gdbus-codegen                                        \
+               --c-namespace Gsm                               \
+               --interface-prefix=org.gnome.SessionManager             \
+               --generate-c-code=gsm-manager-glue                      \
+               --annotate "org.gnome.SessionManager"                   \
+                          "org.gtk.GDBus.C.Name" Manager               \
+               $(srcdir)/org.gnome.SessionManager.xml
+
+test_greeter_login_window_SOURCES =    \
+       test-greeter-login-window.c     \
+       gdm-timer.h                     \
+       gdm-timer.c                     \
+       gdm-greeter-login-window.h      \
+       gdm-greeter-login-window.c      \
+       gdm-scrollable-widget.h         \
+       gdm-scrollable-widget.c         \
+       gdm-chooser-widget.h            \
+       gdm-chooser-widget.c            \
+       gdm-sessions.h                  \
+       gdm-sessions.c                  \
+       gdm-cell-renderer-timer.h       \
+       gdm-cell-renderer-timer.c       \
+       gdm-option-widget.h             \
+       gdm-option-widget.c             \
+       gdm-session-option-widget.h     \
+       gdm-session-option-widget.c     \
+       gdm-user-chooser-widget.h       \
+       gdm-user-chooser-widget.c       \
+       gdm-user-chooser-dialog.h       \
+       gdm-user-chooser-dialog.c       \
+       gdm-extension-list.h            \
+       gdm-extension-list.c            \
+       $(NULL)
+
+test_greeter_login_window_LDADD =      \
+       $(top_builddir)/common/libgdmcommon.la  \
+       $(top_builddir)/gui/libgdm/libgdm.la    \
+       $(top_builddir)/gui/simple-greeter/libgdmsimplegreeter/libgdmsimplegreeter.la   \
+       $(top_builddir)/gui/simple-greeter/extensions/unified/libunified.la     \
+       $(COMMON_LIBS)                  \
+       $(SIMPLE_GREETER_LIBS)          \
+       $(RBAC_LIBS)                    \
+       $(NULL)
+
+test_greeter_panel_SOURCES =   \
+       test-greeter-panel.c    \
+       gdm-greeter-panel.h     \
+       gdm-greeter-panel.c     \
+       gdm-clock-widget.h      \
+       gdm-clock-widget.c      \
+       gdm-option-widget.h     \
+       gdm-option-widget.c     \
+       gdm-cell-renderer-timer.h       \
+       gdm-cell-renderer-timer.c       \
+       gdm-timer.h                     \
+       gdm-timer.c                     \
+       gdm-scrollable-widget.h         \
+       gdm-scrollable-widget.c         \
+       gdm-chooser-widget.h            \
+       gdm-chooser-widget.c            \
+       gdm-sessions.h                  \
+       gdm-sessions.c                  \
+       gdm-session-option-widget.h     \
+       gdm-session-option-widget.c     \
+       $(NULL)
+
+test_greeter_panel_LDADD =     \
+       $(top_builddir)/common/libgdmcommon.la  \
+       $(top_builddir)/gui/simple-greeter/libgdmsimplegreeter/libgdmsimplegreeter.la   \
+       $(SIMPLE_GREETER_LIBS)          \
+       $(GTK_LIBS)                     \
+       $(UPOWER_LIBS)          \
+       $(NULL)
+
+test_remote_login_window_SOURCES =     \
+       test-remote-login-window.c      \
+       gdm-remote-login-window.h       \
+       gdm-remote-login-window.c       \
+       $(NULL)
+
+test_remote_login_window_LDADD =       \
+       $(GTK_LIBS)                     \
+       $(top_builddir)/common/libgdmcommon.la  \
+       $(NULL)
+
+test_filesystem_type_SOURCES =         \
+       test-filesystem-type.c  \
+       $(NULL)
+
+test_filesystem_type_LDADD =   \
+       $(COMMON_LIBS)                  \
+       $(NULL)
+
+test_sessions_SOURCES =                \
+       test-sessions.c         \
+       gdm-sessions.h                  \
+       gdm-sessions.c                  \
+       $(NULL)
+
+test_sessions_LDADD =          \
+       $(GTK_LIBS)             \
+       $(NULL)
+
+test_user_chooser_SOURCES =            \
+       test-user-chooser.c             \
+       gdm-timer.h                     \
+       gdm-timer.c                     \
+       gdm-cell-renderer-timer.h       \
+       gdm-cell-renderer-timer.c       \
+       gdm-scrollable-widget.h         \
+       gdm-scrollable-widget.c         \
+       gdm-chooser-widget.h            \
+       gdm-chooser-widget.c            \
+       gdm-user-chooser-widget.h       \
+       gdm-user-chooser-widget.c       \
+       gdm-user-chooser-dialog.h       \
+       gdm-user-chooser-dialog.c       \
+       $(NULL)
+
+test_user_chooser_LDADD =      \
+       $(top_builddir)/common/libgdmcommon.la  \
+       $(COMMON_LIBS)          \
+       $(SIMPLE_GREETER_LIBS)  \
+       $(NULL)
+
+test_user_manager_SOURCES =    \
+       test-user-manager.c     \
+       $(NULL)
+
+test_user_manager_LDADD =      \
+       $(top_builddir)/common/libgdmcommon.la  \
+       $(COMMON_LIBS)          \
+       $(SIMPLE_GREETER_LIBS)  \
+       $(NULL)
+
+libexec_PROGRAMS =                     \
+       gdm-simple-greeter
+
+gdm_simple_greeter_SOURCES =           \
+       greeter-main.c                  \
+       gdm-timer.h                     \
+       gdm-timer.c                     \
+       gdm-cell-renderer-timer.h       \
+       gdm-cell-renderer-timer.c       \
+       gdm-scrollable-widget.h         \
+       gdm-scrollable-widget.c         \
+       gdm-chooser-widget.h            \
+       gdm-chooser-widget.c            \
+       gdm-greeter-session.h           \
+       gdm-greeter-session.c           \
+       gdm-greeter-login-window.c      \
+       gdm-greeter-login-window.h      \
+       gdm-remote-login-window.c       \
+       gdm-remote-login-window.h       \
+       gdm-greeter-panel.h             \
+       gdm-greeter-panel.c             \
+       gdm-clock-widget.h              \
+       gdm-clock-widget.c              \
+       gdm-option-widget.h             \
+       gdm-option-widget.c             \
+       gdm-sessions.h                  \
+       gdm-sessions.c                  \
+       gdm-session-option-widget.h     \
+       gdm-session-option-widget.c     \
+       gdm-user-chooser-widget.h       \
+       gdm-user-chooser-widget.c       \
+       gdm-extension-list.h            \
+       gdm-extension-list.c            \
+       $(NULL)
+
+gdm_simple_greeter_LDADD =             \
+       $(top_builddir)/common/libgdmcommon.la  \
+       $(top_builddir)/gui/libgdm/libgdm.la    \
+       $(top_builddir)/gui/simple-greeter/libgdmsimplegreeter/libgdmsimplegreeter.la   \
+       $(top_builddir)/gui/simple-greeter/extensions/unified/libunified.la     \
+       $(COMMON_LIBS)                  \
+       $(EXTRA_GREETER_LIBS)           \
+       $(SIMPLE_GREETER_LIBS)          \
+       $(RBAC_LIBS)                    \
+       $(UPOWER_LIBS)          \
+       $(NULL)
+
+nodist_gdm_simple_greeter_SOURCES =     \
+       gsm-manager-glue.c              \
+       gsm-manager-glue.h              \
+       gsm-client-glue.c               \
+       gsm-client-glue.h
+
+CLEANFILES = \
+       gsm-manager-glue.c              \
+       gsm-manager-glue.h              \
+       gsm-client-glue.c               \
+       gsm-client-glue.h
+
+BUILT_SOURCES = gsm-client-glue.h gsm-manager-glue.h
+
+uidir = $(pkgdatadir)
+ui_DATA =                                      \
+       gdm-greeter-login-window.ui             \
+       $(NULL)
+
+EXTRA_DIST =                                           \
+       org.gnome.SessionManager.ClientPrivate.xml      \
+       org.gnome.SessionManager.xml                    \
+       $(ui_DATA)                                      \
+       $(NULL)
+
+MAINTAINERCLEANFILES =                  \
+        *~                              \
+        Makefile.in
diff --git a/gui/simple-greeter/extensions/Makefile.am b/gui/simple-greeter/extensions/Makefile.am
new file mode 100644
index 0000000..2cba7ec
--- /dev/null
+++ b/gui/simple-greeter/extensions/Makefile.am
@@ -0,0 +1,5 @@
+SUBDIRS = unified
+
+if ENABLE_SPLIT_AUTHENTICATION
+SUBDIRS += password fingerprint smartcard
+endif
diff --git a/gui/simple-greeter/extensions/fingerprint/Makefile.am 
b/gui/simple-greeter/extensions/fingerprint/Makefile.am
new file mode 100644
index 0000000..3466c87
--- /dev/null
+++ b/gui/simple-greeter/extensions/fingerprint/Makefile.am
@@ -0,0 +1,46 @@
+SUBDIRS = icons
+
+NULL =
+PAM_SERVICE_NAME = gdm-fingerprint
+
+extensiondir = $(GDM_SIMPLE_GREETER_EXTENSIONS_DATA_DIR)/fingerprint
+extension_DATA = page.ui
+
+AM_CPPFLAGS = \
+       -I$(top_srcdir)/common                          \
+       -I$(top_srcdir)/gui/simple-greeter/libgdmsimplegreeter  \
+       -DDMCONFDIR=\""$(dmconfdir)"\"                  \
+       -DGDMCONFDIR=\"$(gdmconfdir)\"                  \
+       -DPLUGINDATADIR=\""$(extensiondir)"\"           \
+       -DGDM_FINGERPRINT_EXTENSION_SERVICE_NAME=\""$(PAM_SERVICE_NAME)"\"      \
+       -DSYSCONFDIR=\""$(sysconfdir)"\"                \
+       -DLIBLOCALEDIR=\""$(prefix)/lib/locale"\"       \
+       -DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
+       -DLIBEXECDIR=\""$(libexecdir)"\"                \
+       -DSBINDIR=\""$(sbindir)"\"                      \
+       $(DISABLE_DEPRECATED_CFLAGS)    \
+       $(GTK_CFLAGS)                                   \
+       $(SIMPLE_GREETER_CFLAGS)                        \
+       $(POLKIT_GNOME_CFLAGS)                          \
+       $(NULL)
+
+
+plugindir = $(GDM_SIMPLE_GREETER_PLUGINS_DIR)
+plugin_LTLIBRARIES = libfingerprint.la
+
+libfingerprint_la_CFLAGS =                     \
+       $(SIMPLE_GREETER_CFLAGS)        \
+       $(NULL)
+
+libfingerprint_la_LDFLAGS = -module -avoid-version -export-dynamic
+libfingerprint_la_LIBADD = ../../../../common/libgdmcommon.la \
+                       ../../libgdmsimplegreeter/libgdmsimplegreeter.la
+libfingerprint_la_SOURCES =                            \
+                       gdm-fingerprint-extension.h     \
+                       gdm-fingerprint-extension.c
+
+EXTRA_DIST = $(extension_DATA) $(gsettings_SCHEMAS)
+
+MAINTAINERCLEANFILES =                  \
+        *~                              \
+        Makefile.in
diff --git a/gui/simple-greeter/extensions/fingerprint/gdm-fingerprint-extension.c 
b/gui/simple-greeter/extensions/fingerprint/gdm-fingerprint-extension.c
new file mode 100644
index 0000000..29aca45
--- /dev/null
+++ b/gui/simple-greeter/extensions/fingerprint/gdm-fingerprint-extension.c
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Written By: Ray Strode <rstrode redhat com>
+ *
+ */
+
+#include <config.h>
+#include <stdlib.h>
+
+#include "gdm-fingerprint-extension.h"
+
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+struct _GdmFingerprintExtensionPrivate
+{
+        GIcon     *icon;
+        GtkWidget *page;
+        GtkActionGroup *actions;
+        GSettings *settings;
+
+        GtkWidget *message_label;
+        GtkWidget *prompt_label;
+        GtkWidget *prompt_entry;
+
+        GQueue    *message_queue;
+        guint      message_timeout_id;
+
+        GDBusConnection *bus_connection;
+
+        guint      answer_pending : 1;
+};
+
+typedef struct {
+        char                  *text;
+        GdmServiceMessageType  type;
+} QueuedMessage;
+
+static void gdm_fingerprint_extension_finalize (GObject *object);
+
+static void gdm_login_extension_iface_init (GdmLoginExtensionIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GdmFingerprintExtension,
+                         gdm_fingerprint_extension,
+                         G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDM_TYPE_LOGIN_EXTENSION,
+                                                gdm_login_extension_iface_init));
+
+static void
+set_message (GdmFingerprintExtension *extension,
+             const char           *message)
+{
+        gtk_widget_show (extension->priv->message_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->message_label), message);
+}
+
+static void
+free_queued_message (QueuedMessage *message)
+{
+        g_free (message->text);
+        g_slice_free (QueuedMessage, message);
+}
+
+static void
+purge_message_queue (GdmFingerprintExtension *extension)
+{
+        if (extension->priv->message_timeout_id) {
+                g_source_remove (extension->priv->message_timeout_id);
+                extension->priv->message_timeout_id = 0;
+        }
+        g_queue_foreach (extension->priv->message_queue,
+                         (GFunc) free_queued_message,
+                         NULL);
+        g_queue_clear (extension->priv->message_queue);
+}
+
+static gboolean
+dequeue_message (GdmFingerprintExtension *extension)
+{
+        if (!g_queue_is_empty (extension->priv->message_queue)) {
+                int duration;
+                gboolean needs_beep;
+
+                QueuedMessage *message;
+                message = (QueuedMessage *) g_queue_pop_head (extension->priv->message_queue);
+
+                switch (message->type) {
+                        case GDM_SERVICE_MESSAGE_TYPE_INFO:
+                                needs_beep = FALSE;
+                                break;
+                        case GDM_SERVICE_MESSAGE_TYPE_PROBLEM:
+                                needs_beep = TRUE;
+                                break;
+                        default:
+                                g_assert_not_reached ();
+                }
+
+                set_message (extension, message->text);
+
+                duration = (int) (g_utf8_strlen (message->text, -1) / 66.0) * 1000;
+                duration = CLAMP (duration, 400, 3000);
+
+                extension->priv->message_timeout_id = g_timeout_add (duration,
+                                                                     (GSourceFunc) dequeue_message,
+                                                                     extension);
+                if (needs_beep) {
+                        gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (extension)));
+                }
+
+                free_queued_message (message);
+        } else {
+                extension->priv->message_timeout_id = 0;
+
+                _gdm_login_extension_emit_message_queue_empty (GDM_LOGIN_EXTENSION (extension));
+        }
+
+        return FALSE;
+}
+
+static void
+gdm_fingerprint_extension_queue_message (GdmLoginExtension *login_extension,
+                                         GdmServiceMessageType type,
+                                         const char      *text)
+{
+        GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension);
+
+        QueuedMessage *message = g_slice_new (QueuedMessage);
+
+        message->text = g_strdup (text);
+        message->type = type;
+
+        g_queue_push_tail (extension->priv->message_queue, message);
+
+        if (extension->priv->message_timeout_id == 0) {
+                dequeue_message (extension);
+        }
+}
+
+static void
+gdm_fingerprint_extension_ask_question (GdmLoginExtension *login_extension,
+                                        const char      *message)
+{
+        GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension);
+        gtk_widget_show (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE);
+        gtk_widget_show (extension->priv->prompt_entry);
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        extension->priv->answer_pending = TRUE;
+}
+
+static void
+gdm_fingerprint_extension_ask_secret (GdmLoginExtension *login_extension,
+                                      const char      *message)
+{
+        GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension);
+        gtk_widget_show (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message);
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), FALSE);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_widget_show (extension->priv->prompt_entry);
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        extension->priv->answer_pending = TRUE;
+}
+
+static void
+gdm_fingerprint_extension_reset (GdmLoginExtension *login_extension)
+{
+        GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension);
+        gtk_widget_hide (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), "");
+
+        gtk_widget_hide (extension->priv->prompt_entry);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE);
+        extension->priv->answer_pending = FALSE;
+
+        set_message (extension, "");
+        purge_message_queue (extension);
+
+        gdm_login_extension_set_enabled (login_extension, FALSE);
+}
+
+static void
+gdm_fingerprint_extension_set_ready (GdmLoginExtension *login_extension)
+{
+        gdm_login_extension_set_enabled (login_extension, TRUE);
+}
+
+static char *
+gdm_fingerprint_extension_get_service_name (GdmLoginExtension *login_extension)
+{
+        return g_strdup (GDM_FINGERPRINT_EXTENSION_SERVICE_NAME);
+}
+
+static GtkWidget *
+gdm_fingerprint_extension_get_page (GdmLoginExtension *login_extension)
+{
+        GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension);
+        return extension->priv->page;
+}
+
+static GtkActionGroup *
+gdm_fingerprint_extension_get_actions (GdmLoginExtension *login_extension)
+{
+        GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension);
+
+        return g_object_ref (extension->priv->actions);
+}
+
+static void
+gdm_fingerprint_extension_request_answer (GdmLoginExtension *login_extension)
+{
+        GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension);
+        const char *text;
+
+        if (!extension->priv->answer_pending) {
+                _gdm_login_extension_emit_answer (login_extension, NULL);
+                return;
+        }
+
+        extension->priv->answer_pending = FALSE;
+        text = gtk_entry_get_text (GTK_ENTRY (extension->priv->prompt_entry));
+        _gdm_login_extension_emit_answer (login_extension, text);
+
+        gtk_widget_hide (extension->priv->prompt_entry);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), "");
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+}
+
+static gboolean
+gdm_fingerprint_extension_focus (GdmLoginExtension *login_extension)
+{
+        GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension);
+
+        if (!extension->priv->answer_pending) {
+                return FALSE;
+        }
+
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        return TRUE;
+}
+
+static gboolean
+gdm_fingerprint_extension_has_queued_messages (GdmLoginExtension *login_extension)
+{
+        GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension);
+
+        if (extension->priv->message_timeout_id != 0) {
+                return TRUE;
+        }
+
+        if (!g_queue_is_empty (extension->priv->message_queue)) {
+                return TRUE;
+        }
+
+        return FALSE;
+}
+
+static GIcon *
+gdm_fingerprint_extension_get_icon (GdmLoginExtension *login_extension)
+{
+        GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension);
+        return g_object_ref (extension->priv->icon);
+}
+
+static char *
+gdm_fingerprint_extension_get_name (GdmLoginExtension *extension)
+{
+        return g_strdup (_("Fingerprint Authentication"));
+}
+
+static char *
+gdm_fingerprint_extension_get_description (GdmLoginExtension *extension)
+{
+        return g_strdup (_("Log into session with fingerprint"));
+}
+
+static gboolean
+gdm_fingerprint_extension_is_choosable (GdmLoginExtension *extension)
+{
+        return FALSE;
+}
+
+static gboolean
+gdm_fingerprint_extension_is_visible (GdmLoginExtension *login_extension)
+{
+        GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension);
+        GVariant *device_variant;
+        char *contents, **lines;
+        int i;
+
+        if (!g_settings_get_boolean (extension->priv->settings, "enable-fingerprint-authentication")) {
+                return FALSE;
+        }
+
+        if (extension->priv->bus_connection == NULL) {
+                return FALSE;
+        }
+
+        device_variant =
+            g_dbus_connection_call_sync (extension->priv->bus_connection,
+                                         "net.reactivated.Fprint",
+                                         "/net/reactivated/Fprint/Manager",
+                                         "net.reactivated.Fprint.Manager",
+                                         "GetDefaultDevice",
+                                         NULL, G_VARIANT_TYPE_OBJECT_PATH,
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1,
+                                         NULL,
+                                         NULL);
+        if (device_variant == NULL) {
+                return FALSE;
+        }
+
+        g_variant_unref (device_variant);
+
+        return TRUE;
+}
+
+static void
+gdm_login_extension_iface_init (GdmLoginExtensionIface *iface)
+{
+        iface->get_icon = gdm_fingerprint_extension_get_icon;
+        iface->get_description = gdm_fingerprint_extension_get_description;
+        iface->get_name = gdm_fingerprint_extension_get_name;
+        iface->is_choosable = gdm_fingerprint_extension_is_choosable;
+        iface->is_visible = gdm_fingerprint_extension_is_visible;
+        iface->queue_message = gdm_fingerprint_extension_queue_message;
+        iface->ask_question = gdm_fingerprint_extension_ask_question;
+        iface->ask_secret = gdm_fingerprint_extension_ask_secret;
+        iface->reset = gdm_fingerprint_extension_reset;
+        iface->set_ready = gdm_fingerprint_extension_set_ready;
+        iface->get_service_name = gdm_fingerprint_extension_get_service_name;
+        iface->get_page = gdm_fingerprint_extension_get_page;
+        iface->get_actions = gdm_fingerprint_extension_get_actions;
+        iface->request_answer = gdm_fingerprint_extension_request_answer;
+        iface->focus = gdm_fingerprint_extension_focus;
+        iface->has_queued_messages = gdm_fingerprint_extension_has_queued_messages;
+}
+
+static void
+gdm_fingerprint_extension_class_init (GdmFingerprintExtensionClass *extension_class)
+{
+        GObjectClass *object_class;
+
+        object_class = G_OBJECT_CLASS (extension_class);
+
+        object_class->finalize = gdm_fingerprint_extension_finalize;
+
+        g_type_class_add_private (extension_class,
+                                  sizeof (GdmFingerprintExtensionPrivate));
+}
+
+static void
+gdm_fingerprint_extension_finalize (GObject *object)
+{
+        GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (object);
+
+        purge_message_queue (extension);
+
+        if (extension->priv->bus_connection != NULL) {
+                g_object_unref (extension->priv->bus_connection);
+        }
+}
+
+static void
+create_page (GdmFingerprintExtension *extension)
+{
+        GtkBuilder *builder;
+        GObject *object;
+        GError *error;
+
+        builder = gtk_builder_new ();
+
+        error = NULL;
+        gtk_builder_add_from_file (builder,
+                                   PLUGINDATADIR "/page.ui",
+                                   &error);
+
+        if (error != NULL) {
+                g_warning ("Could not load UI file: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        object = gtk_builder_get_object (builder, "page");
+        g_object_ref (object);
+
+        extension->priv->page = GTK_WIDGET (object);
+
+        object = gtk_builder_get_object (builder, "auth-prompt-label");
+        g_object_ref (object);
+        extension->priv->prompt_label = GTK_WIDGET (object);
+        gtk_widget_hide (extension->priv->prompt_label);
+
+        object = gtk_builder_get_object (builder, "auth-prompt-entry");
+        g_object_ref (object);
+        extension->priv->prompt_entry = GTK_WIDGET (object);
+        gtk_widget_hide (extension->priv->prompt_entry);
+
+        object = gtk_builder_get_object (builder, "auth-message-label");
+        g_object_ref (object);
+        extension->priv->message_label = GTK_WIDGET (object);
+        gtk_widget_show (extension->priv->message_label);
+
+        g_object_unref (builder);
+}
+
+static void
+create_actions (GdmFingerprintExtension *extension)
+{
+        extension->priv->actions = gtk_action_group_new (GDM_FINGERPRINT_EXTENSION_NAME);
+}
+
+static void
+gdm_fingerprint_extension_init (GdmFingerprintExtension *extension)
+{
+        extension->priv = G_TYPE_INSTANCE_GET_PRIVATE (extension,
+                                                       GDM_TYPE_FINGERPRINT_EXTENSION,
+                                                       GdmFingerprintExtensionPrivate);
+
+        extension->priv->icon = g_themed_icon_new ("gdm-fingerprint");
+        create_page (extension);
+        create_actions (extension);
+
+        extension->priv->message_queue = g_queue_new ();
+
+        extension->priv->settings = g_settings_new ("org.gnome.login-screen");
+        extension->priv->bus_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL);
+
+        gdm_fingerprint_extension_reset (GDM_LOGIN_EXTENSION (extension));
+}
+
+void
+g_io_module_load (GIOModule *module)
+{
+        g_io_extension_point_implement (GDM_LOGIN_EXTENSION_POINT_NAME,
+                                        GDM_TYPE_FINGERPRINT_EXTENSION,
+                                        GDM_FINGERPRINT_EXTENSION_NAME,
+                                        0);
+}
+
+void
+g_io_module_unload (GIOModule *module)
+{
+}
diff --git a/gui/simple-greeter/extensions/fingerprint/gdm-fingerprint-extension.h 
b/gui/simple-greeter/extensions/fingerprint/gdm-fingerprint-extension.h
new file mode 100644
index 0000000..e6cba4e
--- /dev/null
+++ b/gui/simple-greeter/extensions/fingerprint/gdm-fingerprint-extension.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * Written By: Ray Strode <rstrode redhat com>
+ */
+
+#ifndef __GDM_FINGERPRINT_EXTENSION_H
+#define __GDM_FINGERPRINT_EXTENSION_H
+
+#include <glib-object.h>
+#include "gdm-login-extension.h"
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_FINGERPRINT_EXTENSION (gdm_fingerprint_extension_get_type ())
+#define GDM_FINGERPRINT_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_FINGERPRINT_EXTENSION, 
GdmFingerprintExtension))
+#define GDM_FINGERPRINT_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), 
GDM_TYPE_FINGERPRINT_EXTENSION, GdmFingerprintExtensionClass))
+#define GDM_IS_FINGERPRINT_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GDM_TYPE_FINGERPRINT_EXTENSION))
+#define GDM_IS_FINGERPRINT_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GDM_TYPE_FINGERPRINT_EXTENSION))
+#define GDM_FINGERPRINT_EXTENSION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), 
GDM_TYPE_FINGERPRINT_EXTENSION, GdmFingerprintExtensionClass))
+
+#define GDM_FINGERPRINT_EXTENSION_NAME "gdm-fingerprint-extension"
+
+typedef struct _GdmFingerprintExtensionPrivate GdmFingerprintExtensionPrivate;
+
+typedef struct
+{
+        GObject                  parent;
+        GdmFingerprintExtensionPrivate *priv;
+} GdmFingerprintExtension;
+
+typedef struct
+{
+        GObjectClass parent_class;
+} GdmFingerprintExtensionClass;
+
+GType                 gdm_fingerprint_extension_get_type      (void);
+
+G_END_DECLS
+
+#endif /* GDM_FINGERPRINT_EXTENSION_H */
diff --git a/gui/simple-greeter/extensions/fingerprint/icons/16x16/Makefile.am 
b/gui/simple-greeter/extensions/fingerprint/icons/16x16/Makefile.am
new file mode 100644
index 0000000..f42e317
--- /dev/null
+++ b/gui/simple-greeter/extensions/fingerprint/icons/16x16/Makefile.am
@@ -0,0 +1,5 @@
+iconsdir = $(datadir)/icons/hicolor/16x16/apps
+
+icons_DATA = gdm-fingerprint.png
+
+EXTRA_DIST = $(icons_DATA)
diff --git a/gui/simple-greeter/extensions/fingerprint/icons/16x16/gdm-fingerprint.png 
b/gui/simple-greeter/extensions/fingerprint/icons/16x16/gdm-fingerprint.png
new file mode 100644
index 0000000..4438cee
Binary files /dev/null and b/gui/simple-greeter/extensions/fingerprint/icons/16x16/gdm-fingerprint.png differ
diff --git a/gui/simple-greeter/extensions/fingerprint/icons/48x48/Makefile.am 
b/gui/simple-greeter/extensions/fingerprint/icons/48x48/Makefile.am
new file mode 100644
index 0000000..f4ab2a0
--- /dev/null
+++ b/gui/simple-greeter/extensions/fingerprint/icons/48x48/Makefile.am
@@ -0,0 +1,5 @@
+iconsdir = $(datadir)/icons/hicolor/48x48/apps
+
+icons_DATA = gdm-fingerprint.png
+
+EXTRA_DIST = $(icons_DATA)
diff --git a/gui/simple-greeter/extensions/fingerprint/icons/48x48/gdm-fingerprint.png 
b/gui/simple-greeter/extensions/fingerprint/icons/48x48/gdm-fingerprint.png
new file mode 100644
index 0000000..fd6f546
Binary files /dev/null and b/gui/simple-greeter/extensions/fingerprint/icons/48x48/gdm-fingerprint.png differ
diff --git a/gui/simple-greeter/extensions/fingerprint/icons/Makefile.am 
b/gui/simple-greeter/extensions/fingerprint/icons/Makefile.am
new file mode 100644
index 0000000..c20f10d
--- /dev/null
+++ b/gui/simple-greeter/extensions/fingerprint/icons/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = 16x16 48x48
diff --git a/gui/simple-greeter/extensions/fingerprint/page.ui 
b/gui/simple-greeter/extensions/fingerprint/page.ui
new file mode 100644
index 0000000..8fa5c7b
--- /dev/null
+++ b/gui/simple-greeter/extensions/fingerprint/page.ui
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.14"/>
+    <object class="GtkVBox" id="page">
+      <property name="visible">True</property>
+      <property name="orientation">vertical</property>
+      <child>
+        <object class="GtkHBox" id="auth-input-box">
+          <property name="visible">True</property>
+          <property name="spacing">6</property>
+          <child>
+            <object class="GtkLabel" id="auth-prompt-label">
+              <property name="visible">True</property>
+            </object>
+            <packing>
+              <property name="expand">False</property>
+              <property name="fill">False</property>
+              <property name="position">0</property>
+            </packing>
+          </child>
+          <child>
+            <object class="GtkEntry" id="auth-prompt-entry">
+              <property name="visible">True</property>
+              <property name="can_focus">True</property>
+              <property name="activates_default">True</property>
+            </object>
+            <packing>
+              <property name="position">1</property>
+            </packing>
+          </child>
+        </object>
+        <packing>
+          <property name="expand">True</property>
+          <property name="fill">True</property>
+          <property name="position">0</property>
+        </packing>
+      </child>
+      <child>
+        <object class="GtkHBox" id="auth-message-box">
+          <property name="visible">True</property>
+          <child>
+            <object class="GtkLabel" id="auth-message-label">
+              <property name="visible">True</property>
+            </object>
+            <packing>
+              <property name="position">0</property>
+            </packing>
+          </child>
+        </object>
+        <packing>
+          <property name="expand">True</property>
+          <property name="fill">True</property>
+          <property name="position">1</property>
+        </packing>
+      </child>
+    </object>
+</interface>
diff --git a/gui/simple-greeter/extensions/password/Makefile.am 
b/gui/simple-greeter/extensions/password/Makefile.am
new file mode 100644
index 0000000..dd3f75b
--- /dev/null
+++ b/gui/simple-greeter/extensions/password/Makefile.am
@@ -0,0 +1,43 @@
+NULL =
+PAM_SERVICE_NAME = gdm-password
+
+extensiondir = $(GDM_SIMPLE_GREETER_EXTENSIONS_DATA_DIR)/password
+extension_DATA = page.ui
+
+AM_CPPFLAGS = \
+       -I$(top_srcdir)/common                          \
+       -I$(top_srcdir)/gui/simple-greeter/libgdmsimplegreeter  \
+       -DDMCONFDIR=\""$(dmconfdir)"\"                  \
+       -DGDMCONFDIR=\"$(gdmconfdir)\"                  \
+       -DPLUGINDATADIR=\""$(extensiondir)"\"           \
+       -DGDM_PASSWORD_EXTENSION_SERVICE_NAME=\""$(PAM_SERVICE_NAME)"\" \
+       -DSYSCONFDIR=\""$(sysconfdir)"\"                \
+       -DLIBLOCALEDIR=\""$(prefix)/lib/locale"\"       \
+       -DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
+       -DLIBEXECDIR=\""$(libexecdir)"\"                \
+       -DSBINDIR=\""$(sbindir)"\"                      \
+       $(DISABLE_DEPRECATED_CFLAGS)    \
+       $(GTK_CFLAGS)                                   \
+       $(SIMPLE_GREETER_CFLAGS)                        \
+       $(POLKIT_GNOME_CFLAGS)                          \
+       $(NULL)
+
+plugindir = $(GDM_SIMPLE_GREETER_PLUGINS_DIR)
+plugin_LTLIBRARIES = libpassword.la
+
+libpassword_la_CFLAGS =                        \
+       $(SIMPLE_GREETER_CFLAGS)        \
+       $(NULL)
+
+libpassword_la_LDFLAGS = -module -avoid-version -export-dynamic
+libpassword_la_LIBADD = ../../../../common/libgdmcommon.la \
+                       ../../libgdmsimplegreeter/libgdmsimplegreeter.la
+libpassword_la_SOURCES =                               \
+                       gdm-password-extension.h        \
+                       gdm-password-extension.c
+
+EXTRA_DIST = $(extension_DATA)
+
+MAINTAINERCLEANFILES =                  \
+        *~                              \
+        Makefile.in
diff --git a/gui/simple-greeter/extensions/password/gdm-password-extension.c 
b/gui/simple-greeter/extensions/password/gdm-password-extension.c
new file mode 100644
index 0000000..6e1fc9c
--- /dev/null
+++ b/gui/simple-greeter/extensions/password/gdm-password-extension.c
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Written By: Ray Strode <rstrode redhat com>
+ *
+ */
+
+#include <config.h>
+#include "gdm-password-extension.h"
+#include "gdm-login-extension.h"
+
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+struct _GdmPasswordExtensionPrivate
+{
+        GIcon     *icon;
+        GtkWidget *page;
+        GtkActionGroup *actions;
+        GtkAction *login_action;
+
+        GtkWidget *message_label;
+        GtkWidget *prompt_label;
+        GtkWidget *prompt_entry;
+
+        GQueue    *message_queue;
+        guint      message_timeout_id;
+
+        guint      answer_pending : 1;
+};
+
+typedef struct {
+        char                       *text;
+        GdmServiceMessageType  type;
+} QueuedMessage;
+
+static void gdm_password_extension_finalize (GObject *object);
+
+static void gdm_login_extension_iface_init (GdmLoginExtensionIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GdmPasswordExtension,
+                         gdm_password_extension,
+                         G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDM_TYPE_LOGIN_EXTENSION,
+                                                gdm_login_extension_iface_init));
+
+static void
+set_message (GdmPasswordExtension *extension,
+             const char           *message)
+{
+        gtk_widget_show (extension->priv->message_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->message_label), message);
+}
+
+static void
+free_queued_message (QueuedMessage *message)
+{
+        g_free (message->text);
+        g_slice_free (QueuedMessage, message);
+}
+
+static void
+purge_message_queue (GdmPasswordExtension *extension)
+{
+        if (extension->priv->message_timeout_id) {
+                g_source_remove (extension->priv->message_timeout_id);
+                extension->priv->message_timeout_id = 0;
+        }
+        g_queue_foreach (extension->priv->message_queue,
+                         (GFunc) free_queued_message,
+                         NULL);
+        g_queue_clear (extension->priv->message_queue);
+}
+
+static gboolean
+dequeue_message (GdmPasswordExtension *extension)
+{
+        if (!g_queue_is_empty (extension->priv->message_queue)) {
+                int duration;
+                gboolean needs_beep;
+
+                QueuedMessage *message;
+                message = (QueuedMessage *) g_queue_pop_head (extension->priv->message_queue);
+
+                switch (message->type) {
+                        case GDM_SERVICE_MESSAGE_TYPE_INFO:
+                                needs_beep = FALSE;
+                                break;
+                        case GDM_SERVICE_MESSAGE_TYPE_PROBLEM:
+                                needs_beep = TRUE;
+                                break;
+                        default:
+                                g_assert_not_reached ();
+                }
+
+                set_message (extension, message->text);
+
+                duration = (int) (g_utf8_strlen (message->text, -1) / 66.0) * 1000;
+                duration = CLAMP (duration, 400, 3000);
+
+                extension->priv->message_timeout_id = g_timeout_add (duration,
+                                                                     (GSourceFunc) dequeue_message,
+                                                                     extension);
+                if (needs_beep) {
+                        gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (extension->priv->page)));
+                }
+
+                free_queued_message (message);
+        } else {
+                extension->priv->message_timeout_id = 0;
+
+                _gdm_login_extension_emit_message_queue_empty (GDM_LOGIN_EXTENSION (extension));
+        }
+
+        return FALSE;
+}
+
+static void
+gdm_password_extension_queue_message (GdmLoginExtension   *login_extension,
+                                      GdmServiceMessageType  type,
+                                      const char            *text)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+
+        QueuedMessage *message = g_slice_new (QueuedMessage);
+
+        message->text = g_strdup (text);
+        message->type = type;
+
+        g_queue_push_tail (extension->priv->message_queue, message);
+
+        if (extension->priv->message_timeout_id == 0) {
+                dequeue_message (extension);
+        }
+}
+
+static void
+gdm_password_extension_ask_question (GdmLoginExtension *login_extension,
+                                     const char          *message)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+        gtk_widget_show (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE);
+        gtk_widget_show (extension->priv->prompt_entry);
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        extension->priv->answer_pending = TRUE;
+
+        gtk_action_set_sensitive (extension->priv->login_action, TRUE);
+}
+
+static void
+gdm_password_extension_ask_secret (GdmLoginExtension *login_extension,
+                                   const char          *message)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+        gtk_widget_show (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message);
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), FALSE);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_widget_show (extension->priv->prompt_entry);
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        extension->priv->answer_pending = TRUE;
+
+        gtk_action_set_sensitive (extension->priv->login_action, TRUE);
+}
+
+static void
+gdm_password_extension_reset (GdmLoginExtension *login_extension)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+        gtk_widget_hide (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), "");
+
+        gtk_widget_hide (extension->priv->prompt_entry);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE);
+        extension->priv->answer_pending = FALSE;
+
+        set_message (extension, "");
+        purge_message_queue (extension);
+
+        gdm_login_extension_set_enabled (login_extension, FALSE);
+}
+
+static void
+gdm_password_extension_set_ready (GdmLoginExtension *extension)
+{
+        gdm_login_extension_set_enabled (extension, TRUE);
+}
+
+static char *
+gdm_password_extension_get_service_name (GdmLoginExtension *extension)
+{
+        return g_strdup (GDM_PASSWORD_EXTENSION_SERVICE_NAME);
+}
+
+static GtkWidget *
+gdm_password_extension_get_page (GdmLoginExtension *login_extension)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+        return extension->priv->page;
+}
+
+static GtkActionGroup *
+gdm_password_extension_get_actions (GdmLoginExtension *login_extension)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+        return g_object_ref (extension->priv->actions);
+}
+
+static void
+request_answer (GdmPasswordExtension *extension)
+{
+        const char *text;
+
+        if (!extension->priv->answer_pending) {
+                _gdm_login_extension_emit_answer (GDM_LOGIN_EXTENSION (extension), NULL);
+                return;
+        }
+
+        extension->priv->answer_pending = FALSE;
+        text = gtk_entry_get_text (GTK_ENTRY (extension->priv->prompt_entry));
+        _gdm_login_extension_emit_answer (GDM_LOGIN_EXTENSION (extension), text);
+
+        gtk_widget_hide (extension->priv->prompt_entry);
+        gtk_widget_hide (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), "");
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+}
+
+static gboolean
+gdm_password_extension_focus (GdmLoginExtension *login_extension)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+        if (!extension->priv->answer_pending) {
+                _gdm_login_extension_emit_answer (login_extension, NULL);
+                return FALSE;
+        }
+
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        return TRUE;
+}
+
+static gboolean
+gdm_password_extension_has_queued_messages (GdmLoginExtension *login_extension)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+
+        if (extension->priv->message_timeout_id != 0) {
+                return TRUE;
+        }
+
+        if (!g_queue_is_empty (extension->priv->message_queue)) {
+                return TRUE;
+        }
+
+        return FALSE;
+}
+
+static GIcon *
+gdm_password_extension_get_icon (GdmLoginExtension *login_extension)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension);
+        return g_object_ref (extension->priv->icon);
+}
+
+static char *
+gdm_password_extension_get_name (GdmLoginExtension *login_extension)
+{
+        return g_strdup (_("Password Authentication"));
+}
+
+static char *
+gdm_password_extension_get_description (GdmLoginExtension *login_extension)
+{
+        return g_strdup (_("Log into session with username and password"));
+}
+
+static gboolean
+gdm_password_extension_is_choosable (GdmLoginExtension *login_extension)
+{
+        return FALSE;
+}
+
+static gboolean
+gdm_password_extension_is_visible (GdmLoginExtension *login_extension)
+{
+        return TRUE;
+}
+
+static void
+gdm_login_extension_iface_init (GdmLoginExtensionIface *iface)
+{
+        iface->get_icon = gdm_password_extension_get_icon;
+        iface->get_description = gdm_password_extension_get_description;
+        iface->get_name = gdm_password_extension_get_name;
+        iface->is_choosable = gdm_password_extension_is_choosable;
+        iface->is_visible = gdm_password_extension_is_visible;
+        iface->queue_message = gdm_password_extension_queue_message;
+        iface->ask_question = gdm_password_extension_ask_question;
+        iface->ask_secret = gdm_password_extension_ask_secret;
+        iface->reset = gdm_password_extension_reset;
+        iface->set_ready = gdm_password_extension_set_ready;
+        iface->get_service_name = gdm_password_extension_get_service_name;
+        iface->get_page = gdm_password_extension_get_page;
+        iface->get_actions = gdm_password_extension_get_actions;
+        iface->focus = gdm_password_extension_focus;
+        iface->has_queued_messages = gdm_password_extension_has_queued_messages;
+}
+
+static void
+gdm_password_extension_class_init (GdmPasswordExtensionClass *extension_class)
+{
+        GObjectClass *object_class;
+
+        object_class = G_OBJECT_CLASS (extension_class);
+
+        object_class->finalize = gdm_password_extension_finalize;
+
+        g_type_class_add_private (extension_class,
+                                  sizeof (GdmPasswordExtensionPrivate));
+}
+
+static void
+gdm_password_extension_finalize (GObject *object)
+{
+        GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (object);
+
+        purge_message_queue (extension);
+}
+
+static void
+on_activate_log_in (GdmPasswordExtension *extension,
+                    GtkAction            *action)
+{
+        request_answer (extension);
+        gtk_action_set_sensitive (action, FALSE);
+}
+
+static void
+create_page (GdmPasswordExtension *extension)
+{
+        GtkBuilder *builder;
+        GObject *object;
+        GError *error;
+
+        builder = gtk_builder_new ();
+
+        error = NULL;
+        gtk_builder_add_from_file (builder,
+                                   PLUGINDATADIR "/page.ui",
+                                   &error);
+
+        if (error != NULL) {
+                g_warning ("Could not load UI file: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        object = gtk_builder_get_object (builder, "page");
+        g_object_ref (object);
+
+        extension->priv->page = GTK_WIDGET (object);
+
+        object = gtk_builder_get_object (builder, "auth-prompt-label");
+        g_object_ref (object);
+        extension->priv->prompt_label = GTK_WIDGET (object);
+        gtk_widget_hide (extension->priv->prompt_label);
+
+        object = gtk_builder_get_object (builder, "auth-prompt-entry");
+        g_object_ref (object);
+        extension->priv->prompt_entry = GTK_WIDGET (object);
+        gtk_widget_hide (extension->priv->prompt_entry);
+
+        object = gtk_builder_get_object (builder, "auth-message-label");
+        g_object_ref (object);
+        extension->priv->message_label = GTK_WIDGET (object);
+        gtk_widget_show (extension->priv->message_label);
+
+        g_object_unref (builder);
+}
+
+static void
+create_actions (GdmPasswordExtension *extension)
+{
+        GtkAction *action;
+
+        extension->priv->actions = gtk_action_group_new (GDM_PASSWORD_EXTENSION_NAME);
+
+        action = gtk_action_new (GDM_LOGIN_EXTENSION_DEFAULT_ACTION,
+                                 _("Log In"), NULL, NULL);
+        g_signal_connect_swapped (action, "activate",
+                                  G_CALLBACK (on_activate_log_in), extension);
+        g_object_set (G_OBJECT (action), "icon-name", "go-home", NULL);
+        gtk_action_group_add_action (extension->priv->actions,
+                                     action);
+
+        extension->priv->login_action = action;
+}
+
+static void
+gdm_password_extension_init (GdmPasswordExtension *extension)
+{
+        extension->priv = G_TYPE_INSTANCE_GET_PRIVATE (extension,
+                                                       GDM_TYPE_PASSWORD_EXTENSION,
+                                                       GdmPasswordExtensionPrivate);
+
+        extension->priv->icon = g_themed_icon_new ("dialog-password");
+        create_page (extension);
+        create_actions (extension);
+
+        extension->priv->message_queue = g_queue_new ();
+
+        gdm_password_extension_reset (GDM_LOGIN_EXTENSION (extension));
+}
+
+void
+g_io_module_load (GIOModule *module)
+{
+        g_io_extension_point_implement (GDM_LOGIN_EXTENSION_POINT_NAME,
+                                        GDM_TYPE_PASSWORD_EXTENSION,
+                                        GDM_PASSWORD_EXTENSION_NAME,
+                                        G_MAXINT);
+}
+
+void
+g_io_module_unload (GIOModule *module)
+{
+}
diff --git a/gui/simple-greeter/extensions/password/gdm-password-extension.h 
b/gui/simple-greeter/extensions/password/gdm-password-extension.h
new file mode 100644
index 0000000..6ac6e2e
--- /dev/null
+++ b/gui/simple-greeter/extensions/password/gdm-password-extension.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * Written By: Ray Strode <rstrode redhat com>
+ */
+
+#ifndef __GDM_PASSWORD_EXTENSION_H
+#define __GDM_PASSWORD_EXTENSION_H
+
+#include <glib-object.h>
+#include "gdm-login-extension.h"
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_PASSWORD_EXTENSION (gdm_password_extension_get_type ())
+#define GDM_PASSWORD_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_PASSWORD_EXTENSION, 
GdmPasswordExtension))
+#define GDM_PASSWORD_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_PASSWORD_EXTENSION, 
GdmPasswordExtensionClass))
+#define GDM_IS_PASSWORD_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDM_TYPE_PASSWORD_EXTENSION))
+#define GDM_IS_PASSWORD_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GDM_TYPE_PASSWORD_EXTENSION))
+#define GDM_PASSWORD_EXTENSION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), 
GDM_TYPE_PASSWORD_EXTENSION, GdmPasswordExtensionClass))
+
+#define GDM_PASSWORD_EXTENSION_NAME "gdm-password-extension"
+
+typedef struct _GdmPasswordExtensionPrivate GdmPasswordExtensionPrivate;
+
+typedef struct
+{
+        GObject                  parent;
+        GdmPasswordExtensionPrivate *priv;
+} GdmPasswordExtension;
+
+typedef struct
+{
+        GObjectClass parent_class;
+} GdmPasswordExtensionClass;
+
+GType                 gdm_password_extension_get_type      (void);
+
+G_END_DECLS
+
+#endif /* GDM_PASSWORD_EXTENSION_H */
diff --git a/gui/simple-greeter/extensions/password/page.ui b/gui/simple-greeter/extensions/password/page.ui
new file mode 100644
index 0000000..8fa5c7b
--- /dev/null
+++ b/gui/simple-greeter/extensions/password/page.ui
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.14"/>
+    <object class="GtkVBox" id="page">
+      <property name="visible">True</property>
+      <property name="orientation">vertical</property>
+      <child>
+        <object class="GtkHBox" id="auth-input-box">
+          <property name="visible">True</property>
+          <property name="spacing">6</property>
+          <child>
+            <object class="GtkLabel" id="auth-prompt-label">
+              <property name="visible">True</property>
+            </object>
+            <packing>
+              <property name="expand">False</property>
+              <property name="fill">False</property>
+              <property name="position">0</property>
+            </packing>
+          </child>
+          <child>
+            <object class="GtkEntry" id="auth-prompt-entry">
+              <property name="visible">True</property>
+              <property name="can_focus">True</property>
+              <property name="activates_default">True</property>
+            </object>
+            <packing>
+              <property name="position">1</property>
+            </packing>
+          </child>
+        </object>
+        <packing>
+          <property name="expand">True</property>
+          <property name="fill">True</property>
+          <property name="position">0</property>
+        </packing>
+      </child>
+      <child>
+        <object class="GtkHBox" id="auth-message-box">
+          <property name="visible">True</property>
+          <child>
+            <object class="GtkLabel" id="auth-message-label">
+              <property name="visible">True</property>
+            </object>
+            <packing>
+              <property name="position">0</property>
+            </packing>
+          </child>
+        </object>
+        <packing>
+          <property name="expand">True</property>
+          <property name="fill">True</property>
+          <property name="position">1</property>
+        </packing>
+      </child>
+    </object>
+</interface>
diff --git a/gui/simple-greeter/extensions/smartcard/Makefile.am 
b/gui/simple-greeter/extensions/smartcard/Makefile.am
new file mode 100644
index 0000000..d749cf9
--- /dev/null
+++ b/gui/simple-greeter/extensions/smartcard/Makefile.am
@@ -0,0 +1,66 @@
+SUBDIRS = icons
+
+NULL =
+PAM_SERVICE_NAME = gdm-smartcard
+
+extensiondir = $(GDM_SIMPLE_GREETER_EXTENSIONS_DATA_DIR)/smartcard
+extension_DATA = page.ui
+
+AM_CPPFLAGS = \
+       -I$(top_srcdir)/common                          \
+       -I$(top_srcdir)/gui/simple-greeter/libgdmsimplegreeter  \
+       -DDMCONFDIR=\""$(dmconfdir)"\"                  \
+       -DGDMCONFDIR=\"$(gdmconfdir)\"                  \
+       -DPLUGINDATADIR=\""$(extensiondir)"\"           \
+       -DGDM_SMARTCARD_EXTENSION_SERVICE_NAME=\""$(PAM_SERVICE_NAME)"\"        \
+       -DSYSCONFDIR=\""$(sysconfdir)"\"                \
+       -DLIBLOCALEDIR=\""$(prefix)/lib/locale"\"       \
+       -DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
+       -DLIBEXECDIR=\""$(libexecdir)"\"                \
+       -DLIBDIR=\""$(libdir)"\"                        \
+       -DSBINDIR=\""$(sbindir)"\"                      \
+       $(DISABLE_DEPRECATED_CFLAGS)    \
+       $(GTK_CFLAGS)                                   \
+       $(SIMPLE_GREETER_CFLAGS)                        \
+       $(POLKIT_GNOME_CFLAGS)                          \
+       $(NULL)
+
+plugindir = $(GDM_SIMPLE_GREETER_PLUGINS_DIR)
+plugin_LTLIBRARIES = libsmartcard.la
+
+libsmartcard_la_CFLAGS =                       \
+       $(SIMPLE_GREETER_CFLAGS)        \
+       $(NULL)
+
+libexec_PROGRAMS =                     \
+       gdm-smartcard-worker            \
+       $(NULL)
+
+libsmartcard_la_LDFLAGS = -module -avoid-version -export-dynamic
+libsmartcard_la_LIBADD = ../../../../common/libgdmcommon.la \
+                       ../../libgdmsimplegreeter/libgdmsimplegreeter.la
+libsmartcard_la_SOURCES =                              \
+                       gdm-smartcard-extension.h       \
+                       gdm-smartcard-extension.c
+
+gdm_smartcard_worker_LDADD = ../../../../common/libgdmcommon.la \
+                               $(DAEMON_LIBS)          \
+                               $(GTHREAD_LIBS)         \
+                               $(NSS_LIBS)             \
+                               $(NULL)
+gdm_smartcard_worker_CFLAGS =  $(DAEMON_CFLAGS)        \
+                               $(NSS_CFLAGS)           \
+                               $(NULL)
+gdm_smartcard_worker_SOURCES =                         \
+                               gdm-smartcard.h         \
+                               gdm-smartcard.c         \
+                               gdm-smartcard-manager.h \
+                               gdm-smartcard-manager.c \
+                               gdm-smartcard-worker.c  \
+                               $(NULL)
+
+EXTRA_DIST = $(extension_DATA)
+
+MAINTAINERCLEANFILES =                  \
+        *~                              \
+        Makefile.in
diff --git a/gui/simple-greeter/extensions/smartcard/gdm-smartcard 
b/gui/simple-greeter/extensions/smartcard/gdm-smartcard
new file mode 100644
index 0000000..d5ac1fa
--- /dev/null
+++ b/gui/simple-greeter/extensions/smartcard/gdm-smartcard
@@ -0,0 +1,18 @@
+# Sample PAM file for doing smartcard authentication.
+# Distros should replace this with what makes sense for them.
+auth        required      pam_env.so
+auth        [success=done ignore=ignore default=die] pam_pkcs11.so wait_for_card card_only
+auth        requisite     pam_succeed_if.so uid >= 500 quiet
+auth        required      pam_deny.so
+
+account     required      pam_unix.so
+account     sufficient    pam_localuser.so
+account     sufficient    pam_succeed_if.so uid < 500 quiet
+account     required      pam_permit.so
+
+password    optional      pam_pkcs11.so
+password    requisite     pam_cracklib.so try_first_pass retry=3 type=
+
+session     optional      pam_keyinit.so revoke
+session     required      pam_limits.so
+session     required      pam_unix.so
diff --git a/gui/simple-greeter/extensions/smartcard/gdm-smartcard-extension.c 
b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-extension.c
new file mode 100644
index 0000000..eb33f78
--- /dev/null
+++ b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-extension.c
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Written By: Ray Strode <rstrode redhat com>
+ *
+ */
+
+#include <config.h>
+#include "gdm-smartcard-extension.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#ifndef GDM_SMARTCARD_WORKER_COMMAND
+#define GDM_SMARTCARD_WORKER_COMMAND LIBEXECDIR "/gdm-smartcard-worker"
+#endif
+
+struct _GdmSmartcardExtensionPrivate
+{
+        GIcon     *icon;
+        GtkWidget *page;
+        GtkActionGroup *actions;
+        GtkAction  *login_action;
+        GSettings *settings;
+
+        GtkWidget *message_label;
+        GtkWidget *prompt_label;
+        GtkWidget *prompt_entry;
+
+        GPid       worker_pid;
+        int        number_of_tokens;
+
+        GQueue    *message_queue;
+        guint      message_timeout_id;
+
+        guint      answer_pending : 1;
+        guint      select_when_ready : 1;
+};
+
+typedef struct {
+        char                       *text;
+        GdmServiceMessageType  type;
+} QueuedMessage;
+
+static void gdm_smartcard_extension_finalize (GObject *object);
+
+static void gdm_login_extension_iface_init (GdmLoginExtensionIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GdmSmartcardExtension,
+                         gdm_smartcard_extension,
+                         G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDM_TYPE_LOGIN_EXTENSION,
+                                                gdm_login_extension_iface_init));
+
+static void
+set_message (GdmSmartcardExtension *extension,
+             const char           *message)
+{
+        gtk_widget_show (extension->priv->message_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->message_label), message);
+}
+
+static void
+free_queued_message (QueuedMessage *message)
+{
+        g_free (message->text);
+        g_slice_free (QueuedMessage, message);
+}
+
+static void
+purge_message_queue (GdmSmartcardExtension *extension)
+{
+        if (extension->priv->message_timeout_id) {
+                g_source_remove (extension->priv->message_timeout_id);
+                extension->priv->message_timeout_id = 0;
+        }
+        g_queue_foreach (extension->priv->message_queue,
+                         (GFunc) free_queued_message,
+                         NULL);
+        g_queue_clear (extension->priv->message_queue);
+}
+
+static gboolean
+dequeue_message (GdmSmartcardExtension *extension)
+{
+        if (!g_queue_is_empty (extension->priv->message_queue)) {
+                int duration;
+                gboolean needs_beep;
+
+                QueuedMessage *message;
+                message = (QueuedMessage *) g_queue_pop_head (extension->priv->message_queue);
+
+                switch (message->type) {
+                        case GDM_SERVICE_MESSAGE_TYPE_INFO:
+                                needs_beep = FALSE;
+                                break;
+                        case GDM_SERVICE_MESSAGE_TYPE_PROBLEM:
+                                needs_beep = TRUE;
+                                break;
+                        default:
+                                g_assert_not_reached ();
+                }
+
+                set_message (extension, message->text);
+
+                duration = (int) (g_utf8_strlen (message->text, -1) / 66.0) * 1000;
+                duration = CLAMP (duration, 400, 3000);
+
+                extension->priv->message_timeout_id = g_timeout_add (duration,
+                                                                     (GSourceFunc) dequeue_message,
+                                                                     extension);
+                if (needs_beep) {
+                        gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (extension)));
+                }
+
+                free_queued_message (message);
+        } else {
+                extension->priv->message_timeout_id = 0;
+
+                _gdm_login_extension_emit_message_queue_empty (GDM_LOGIN_EXTENSION (extension));
+        }
+
+        return FALSE;
+}
+
+static void
+gdm_smartcard_extension_queue_message (GdmLoginExtension   *login_extension,
+                                       GdmServiceMessageType  type,
+                                       const char            *text)
+{
+        GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension);
+
+        QueuedMessage *message = g_slice_new (QueuedMessage);
+
+        message->text = g_strdup (text);
+        message->type = type;
+
+        g_queue_push_tail (extension->priv->message_queue, message);
+
+        if (extension->priv->message_timeout_id == 0) {
+                dequeue_message (extension);
+        }
+}
+
+static gboolean
+on_smartcard_event (GIOChannel   *io_channel,
+                    GIOCondition  condition,
+                    gpointer      data)
+{
+        GdmSmartcardExtension *extension;
+
+        extension = GDM_SMARTCARD_EXTENSION (data);
+
+        if (condition & G_IO_IN) {
+                char buffer[1024];
+                ssize_t num_bytes;
+
+                num_bytes = read (g_io_channel_unix_get_fd (io_channel),
+                                  buffer, sizeof (buffer));
+
+                if (num_bytes < 0 && errno != EINTR)
+                        return FALSE;
+
+                if (num_bytes != 1) {
+                        g_debug ("buffer: %s\n", buffer);
+                        return TRUE;
+                }
+
+                if (buffer[0] == 'I') {
+                        extension->priv->number_of_tokens++;
+                } else {
+                        extension->priv->number_of_tokens--;
+                }
+
+                if (extension->priv->number_of_tokens == 1) {
+                        if (!_gdm_login_extension_emit_choose_user (GDM_LOGIN_EXTENSION (extension),
+                                                                    GDM_SMARTCARD_EXTENSION_SERVICE_NAME)) {
+                                g_debug ("could not choose smart card user, cancelling...");
+                                _gdm_login_extension_emit_cancel (GDM_LOGIN_EXTENSION (extension));
+                                extension->priv->select_when_ready = TRUE;
+                        } else {
+                                g_debug ("chose smart card user!");
+                        }
+                } else if (extension->priv->number_of_tokens == 0) {
+                        _gdm_login_extension_emit_cancel (GDM_LOGIN_EXTENSION (extension));
+                }
+
+                return TRUE;
+        }
+
+        if (condition & G_IO_HUP) {
+                return FALSE;
+        }
+
+        return TRUE;
+}
+
+static void
+watch_for_smartcards (GdmSmartcardExtension *extension)
+{
+        GError *error;
+        GIOChannel *io_channel;
+        char *args[] = { GDM_SMARTCARD_WORKER_COMMAND, NULL };
+        GPid pid;
+        int stdout_fd;
+
+        error = NULL;
+
+        if (!g_spawn_async_with_pipes (NULL, args, NULL, 0,
+                                       NULL, NULL, &pid, NULL,
+                                       &stdout_fd, NULL, &error)) {
+                g_debug ("could not start smart card manager: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+        fcntl (stdout_fd, F_SETFD, FD_CLOEXEC);
+
+        io_channel = g_io_channel_unix_new (stdout_fd);
+        g_io_channel_set_flags (io_channel, G_IO_FLAG_NONBLOCK, NULL);
+        g_io_channel_set_encoding (io_channel, NULL, NULL);
+        g_io_channel_set_buffered (io_channel, FALSE);
+        g_io_add_watch (io_channel, G_IO_IN, on_smartcard_event, extension);
+        g_io_channel_set_close_on_unref (io_channel, TRUE);
+        g_io_channel_unref (io_channel);
+
+        extension->priv->worker_pid = pid;
+}
+
+static void
+stop_watching_for_smartcards (GdmSmartcardExtension *extension)
+{
+        kill (extension->priv->worker_pid, SIGTERM);
+}
+
+static void
+gdm_smartcard_extension_ask_question (GdmLoginExtension *login_extension,
+                                      const char          *message)
+{
+        GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension);
+        gtk_widget_show (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE);
+        gtk_widget_show (extension->priv->prompt_entry);
+        gtk_action_set_visible (extension->priv->login_action, TRUE);
+        gtk_action_set_sensitive (extension->priv->login_action, TRUE);
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        extension->priv->answer_pending = TRUE;
+}
+
+static void
+gdm_smartcard_extension_ask_secret (GdmLoginExtension *login_extension,
+                                    const char          *message)
+{
+        GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension);
+        gtk_widget_show (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message);
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), FALSE);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_widget_show (extension->priv->prompt_entry);
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        gtk_action_set_visible (extension->priv->login_action, TRUE);
+        gtk_action_set_sensitive (extension->priv->login_action, TRUE);
+        extension->priv->answer_pending = TRUE;
+}
+
+static void
+gdm_smartcard_extension_reset (GdmLoginExtension *login_extension)
+{
+        GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension);
+        gtk_widget_hide (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), "");
+
+        gtk_widget_hide (extension->priv->prompt_entry);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE);
+        gtk_action_set_visible (extension->priv->login_action, FALSE);
+        extension->priv->answer_pending = FALSE;
+
+        purge_message_queue (extension);
+        set_message (extension, "");
+
+        gdm_login_extension_set_enabled (login_extension, FALSE);
+}
+
+static void
+gdm_smartcard_extension_set_ready (GdmLoginExtension *login_extension)
+{
+        GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension);
+        gdm_login_extension_set_enabled (login_extension, TRUE);
+
+        if (extension->priv->worker_pid <= 0) {
+                watch_for_smartcards (extension);
+        }
+
+        if (extension->priv->select_when_ready) {
+                if (_gdm_login_extension_emit_choose_user (login_extension,
+                                                           GDM_SMARTCARD_EXTENSION_SERVICE_NAME)) {
+                        extension->priv->select_when_ready = FALSE;
+                }
+        }
+}
+
+static char *
+gdm_smartcard_extension_get_service_name (GdmLoginExtension *login_extension)
+{
+        return g_strdup (GDM_SMARTCARD_EXTENSION_SERVICE_NAME);
+}
+
+static GtkWidget *
+gdm_smartcard_extension_get_page (GdmLoginExtension *login_extension)
+{
+        GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension);
+        return extension->priv->page;
+}
+
+static GtkActionGroup *
+gdm_smartcard_extension_get_actions (GdmLoginExtension *login_extension)
+{
+        GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension);
+
+        return g_object_ref (extension->priv->actions);
+}
+
+static void
+request_answer (GdmSmartcardExtension *extension)
+{
+        const char *text;
+
+        if (!extension->priv->answer_pending) {
+                _gdm_login_extension_emit_answer (GDM_LOGIN_EXTENSION (extension), NULL);
+                return;
+        }
+
+        extension->priv->answer_pending = FALSE;
+        text = gtk_entry_get_text (GTK_ENTRY (extension->priv->prompt_entry));
+        _gdm_login_extension_emit_answer (GDM_LOGIN_EXTENSION (extension), text);
+
+        gtk_widget_hide (extension->priv->prompt_entry);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), "");
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_action_set_visible (extension->priv->login_action, FALSE);
+}
+
+static gboolean
+gdm_smartcard_extension_focus (GdmLoginExtension *login_extension)
+{
+        GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension);
+
+        if (!extension->priv->answer_pending) {
+                return FALSE;
+        }
+
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        return TRUE;
+}
+
+static gboolean
+gdm_smartcard_extension_has_queued_messages (GdmLoginExtension *login_extension)
+{
+        GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension);
+
+        if (extension->priv->message_timeout_id != 0) {
+                return TRUE;
+        }
+
+        if (!g_queue_is_empty (extension->priv->message_queue)) {
+                return TRUE;
+        }
+
+        return FALSE;
+}
+
+
+static GIcon *
+gdm_smartcard_extension_get_icon (GdmLoginExtension *login_extension)
+{
+        GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension);
+        return g_object_ref (extension->priv->icon);
+}
+
+static char *
+gdm_smartcard_extension_get_name (GdmLoginExtension *login_extension)
+{
+        return g_strdup (_("Smartcard Authentication"));
+}
+
+static char *
+gdm_smartcard_extension_get_description (GdmLoginExtension *login_extension)
+{
+        return g_strdup (_("Log into session with smartcard"));
+}
+
+static gboolean
+gdm_smartcard_extension_is_choosable (GdmLoginExtension *login_extension)
+{
+        return TRUE;
+}
+
+static gboolean
+gdm_smartcard_extension_is_visible (GdmLoginExtension *login_extension)
+{
+        GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension);
+
+        char *contents, *pid_dir;
+        pid_t pid;
+
+        if (!g_settings_get_boolean (extension->priv->settings, "enable-smartcard-authentication")) {
+                return FALSE;
+        }
+
+        /* FIXME: we should rework things so we find out from the worker that
+         * there's no daemon running instead of like this.
+         */
+        if (g_file_get_contents ("/var/run/pcscd.pid",
+                                 &contents, NULL, NULL) == FALSE) {
+                return FALSE;
+        }
+
+        pid = (pid_t) atoi (contents);
+        g_free (contents);
+
+        if (pid == 0) {
+                return FALSE;
+        }
+
+        pid_dir = g_strdup_printf ("/proc/%d", (int) pid);
+        if (!g_file_test (pid_dir, G_FILE_TEST_EXISTS)) {
+                g_free (pid_dir);
+                return FALSE;
+        }
+        g_free (pid_dir);
+
+        return TRUE;
+}
+
+static void
+gdm_login_extension_iface_init (GdmLoginExtensionIface *iface)
+{
+        iface->get_icon = gdm_smartcard_extension_get_icon;
+        iface->get_description = gdm_smartcard_extension_get_description;
+        iface->get_name = gdm_smartcard_extension_get_name;
+        iface->is_choosable = gdm_smartcard_extension_is_choosable;
+        iface->is_visible = gdm_smartcard_extension_is_visible;
+        iface->queue_message = gdm_smartcard_extension_queue_message;
+        iface->ask_question = gdm_smartcard_extension_ask_question;
+        iface->ask_secret = gdm_smartcard_extension_ask_secret;
+        iface->reset = gdm_smartcard_extension_reset;
+        iface->set_ready = gdm_smartcard_extension_set_ready;
+        iface->get_service_name = gdm_smartcard_extension_get_service_name;
+        iface->get_page = gdm_smartcard_extension_get_page;
+        iface->get_actions = gdm_smartcard_extension_get_actions;
+        iface->focus = gdm_smartcard_extension_focus;
+        iface->has_queued_messages = gdm_smartcard_extension_has_queued_messages;
+}
+
+static void
+gdm_smartcard_extension_class_init (GdmSmartcardExtensionClass *extension_class)
+{
+        GObjectClass *object_class;
+
+        object_class = G_OBJECT_CLASS (extension_class);
+
+        object_class->finalize = gdm_smartcard_extension_finalize;
+
+        g_type_class_add_private (extension_class,
+                                  sizeof (GdmSmartcardExtensionPrivate));
+}
+
+static void
+gdm_smartcard_extension_finalize (GObject *object)
+{
+        GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (object);
+
+        if (extension->priv->worker_pid > 0) {
+                stop_watching_for_smartcards (extension);
+        }
+
+        purge_message_queue (extension);
+}
+
+static void
+create_page (GdmSmartcardExtension *extension)
+{
+        GtkBuilder *builder;
+        GObject *object;
+        GError *error;
+
+        builder = gtk_builder_new ();
+
+        error = NULL;
+        gtk_builder_add_from_file (builder,
+                                   PLUGINDATADIR "/page.ui",
+                                   &error);
+
+        if (error != NULL) {
+                g_warning ("Could not load UI file: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        object = gtk_builder_get_object (builder, "page");
+        g_object_ref (object);
+
+        extension->priv->page = GTK_WIDGET (object);
+
+        object = gtk_builder_get_object (builder, "auth-prompt-label");
+        g_object_ref (object);
+        extension->priv->prompt_label = GTK_WIDGET (object);
+        gtk_widget_hide (extension->priv->prompt_label);
+
+        object = gtk_builder_get_object (builder, "auth-prompt-entry");
+        g_object_ref (object);
+        extension->priv->prompt_entry = GTK_WIDGET (object);
+        gtk_widget_hide (extension->priv->prompt_entry);
+
+        object = gtk_builder_get_object (builder, "auth-message-label");
+        g_object_ref (object);
+        extension->priv->message_label = GTK_WIDGET (object);
+        gtk_widget_show (extension->priv->message_label);
+
+        g_object_unref (builder);
+}
+
+static void
+on_activate_log_in (GdmSmartcardExtension *extension)
+{
+        request_answer (extension);
+        gtk_action_set_sensitive (extension->priv->login_action, FALSE);
+}
+
+static void
+create_actions (GdmSmartcardExtension *extension)
+{
+        GtkAction *action;
+
+        extension->priv->actions = gtk_action_group_new (GDM_SMARTCARD_EXTENSION_NAME);
+
+        action = gtk_action_new (GDM_LOGIN_EXTENSION_DEFAULT_ACTION,
+                                 _("Log In"), NULL, NULL);
+        g_signal_connect_swapped (action, "activate",
+                                  G_CALLBACK (on_activate_log_in), extension);
+        g_object_set (G_OBJECT (action), "icon-name", "go-home", NULL);
+        gtk_action_group_add_action (extension->priv->actions,
+                                     action);
+
+        gtk_action_set_visible (action, FALSE);
+        extension->priv->login_action = action;
+}
+
+static void
+gdm_smartcard_extension_init (GdmSmartcardExtension *extension)
+{
+        extension->priv = G_TYPE_INSTANCE_GET_PRIVATE (extension,
+                                                       GDM_TYPE_SMARTCARD_EXTENSION,
+                                                       GdmSmartcardExtensionPrivate);
+
+        extension->priv->icon = g_themed_icon_new ("gdm-smartcard");
+        create_page (extension);
+        create_actions (extension);
+
+        extension->priv->message_queue = g_queue_new ();
+
+        extension->priv->settings = g_settings_new ("org.gnome.login-screen");
+
+        gdm_smartcard_extension_reset (GDM_LOGIN_EXTENSION (extension));
+}
+
+void
+g_io_module_load (GIOModule *module)
+{
+        g_io_extension_point_implement (GDM_LOGIN_EXTENSION_POINT_NAME,
+                                        GDM_TYPE_SMARTCARD_EXTENSION,
+                                        GDM_SMARTCARD_EXTENSION_NAME,
+                                        0);
+}
+
+void
+g_io_module_unload (GIOModule *module)
+{
+}
diff --git a/gui/simple-greeter/extensions/smartcard/gdm-smartcard-extension.h 
b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-extension.h
new file mode 100644
index 0000000..87f5b86
--- /dev/null
+++ b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-extension.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * Written By: Ray Strode <rstrode redhat com>
+ */
+
+#ifndef __GDM_SMARTCARD_EXTENSION_H
+#define __GDM_SMARTCARD_EXTENSION_H
+
+#include <glib-object.h>
+#include "gdm-login-extension.h"
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_SMARTCARD_EXTENSION (gdm_smartcard_extension_get_type ())
+#define GDM_SMARTCARD_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_SMARTCARD_EXTENSION, 
GdmSmartcardExtension))
+#define GDM_SMARTCARD_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), 
GDM_TYPE_SMARTCARD_EXTENSION, GdmSmartcardExtensionClass))
+#define GDM_IS_SMARTCARD_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDM_TYPE_SMARTCARD_EXTENSION))
+#define GDM_IS_SMARTCARD_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GDM_TYPE_SMARTCARD_EXTENSION))
+#define GDM_SMARTCARD_EXTENSION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), 
GDM_TYPE_SMARTCARD_EXTENSION, GdmSmartcardExtensionClass))
+
+#define GDM_SMARTCARD_EXTENSION_NAME "gdm-smartcard-extension"
+
+typedef struct _GdmSmartcardExtensionPrivate GdmSmartcardExtensionPrivate;
+
+typedef struct
+{
+        GObject                  parent;
+        GdmSmartcardExtensionPrivate *priv;
+} GdmSmartcardExtension;
+
+typedef struct
+{
+        GObjectClass parent_class;
+} GdmSmartcardExtensionClass;
+
+GType                 gdm_smartcard_extension_get_type      (void);
+
+G_END_DECLS
+
+#endif /* GDM_SMARTCARD_EXTENSION_H */
diff --git a/gui/simple-greeter/extensions/smartcard/gdm-smartcard-manager.c 
b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-manager.c
new file mode 100644
index 0000000..6ce5853
--- /dev/null
+++ b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-manager.c
@@ -0,0 +1,1445 @@
+/* gdm-smartcard-manager.c - object for monitoring smartcard insertion and
+ *                           removal events
+ *
+ * Copyright (C) 2006, 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * Written By: Ray Strode
+ */
+#define _GNU_SOURCE
+#include "gdm-smartcard-manager.h"
+
+#define GDM_SMARTCARD_ENABLE_INTERNAL_API
+#include "gdm-smartcard.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib-unix.h>
+#include <glib/gi18n.h>
+
+#include <prerror.h>
+#include <prinit.h>
+#include <nss.h>
+#include <pk11func.h>
+#include <secmod.h>
+#include <secerr.h>
+
+#ifndef GDM_SMARTCARD_MANAGER_DRIVER
+#define GDM_SMARTCARD_MANAGER_DRIVER LIBDIR"/pkcs11/libcoolkeypk11.so"
+#endif
+
+#ifndef GDM_SMARTCARD_MANAGER_NSS_DB
+#define GDM_SMARTCARD_MANAGER_NSS_DB SYSCONFDIR"/pki/nssdb"
+#endif
+
+#ifndef GDM_MAX_OPEN_FILE_DESCRIPTORS
+#define GDM_MAX_OPEN_FILE_DESCRIPTORS 1024
+#endif
+
+#ifndef GDM_OPEN_FILE_DESCRIPTORS_DIR
+#define GDM_OPEN_FILE_DESCRIPTORS_DIR "/proc/self/fd"
+#endif
+
+typedef enum _GdmSmartcardManagerState GdmSmartcardManagerState;
+typedef struct _GdmSmartcardManagerWorker GdmSmartcardManagerWorker;
+
+enum _GdmSmartcardManagerState {
+        GDM_SMARTCARD_MANAGER_STATE_STOPPED = 0,
+        GDM_SMARTCARD_MANAGER_STATE_STARTING,
+        GDM_SMARTCARD_MANAGER_STATE_STARTED,
+        GDM_SMARTCARD_MANAGER_STATE_STOPPING,
+};
+
+struct _GdmSmartcardManagerPrivate {
+        GdmSmartcardManagerState state;
+        GList        *modules;
+        char        *module_path;
+
+        GList        *workers;
+
+        GPid smartcard_event_watcher_pid;
+        GHashTable *smartcards;
+
+        guint poll_timeout_id;
+
+        guint32 is_unstoppable : 1;
+        guint32 nss_is_loaded : 1;
+};
+
+struct _GdmSmartcardManagerWorker {
+        GdmSmartcardManager *manager;
+        gint manager_fd;
+
+        GThread      *thread;
+        SECMODModule *module;
+        GHashTable *smartcards;
+        gint fd;
+        GSource *event_source;
+
+        guint32 nss_is_loaded : 1;
+};
+
+static void gdm_smartcard_manager_finalize (GObject *object);
+static void gdm_smartcard_manager_class_install_signals (GdmSmartcardManagerClass *service_class);
+static void gdm_smartcard_manager_class_install_properties (GdmSmartcardManagerClass *service_class);
+static void gdm_smartcard_manager_set_property (GObject       *object,
+                                                guint          prop_id,
+                                                const GValue  *value,
+                                                GParamSpec    *pspec);
+static void gdm_smartcard_manager_get_property (GObject    *object,
+                                                guint       prop_id,
+                                                GValue     *value,
+                                                GParamSpec *pspec);
+static void gdm_smartcard_manager_set_module_path (GdmSmartcardManager *manager,
+                                                   const char          *module_path);
+static void gdm_smartcard_manager_card_removed_handler (GdmSmartcardManager *manager,
+                                                        GdmSmartcard        *card);
+static void gdm_smartcard_manager_card_inserted_handler (GdmSmartcardManager *manager_class,
+                                                         GdmSmartcard        *card);
+static gboolean gdm_smartcard_manager_stop_now (GdmSmartcardManager *manager);
+static void gdm_smartcard_manager_queue_stop (GdmSmartcardManager *manager);
+
+static GdmSmartcardManagerWorker *gdm_smartcard_manager_create_worker (GdmSmartcardManager  *manager,
+                                                                       SECMODModule         *module);
+
+static GdmSmartcardManagerWorker * gdm_smartcard_manager_worker_new (GdmSmartcardManager *manager,
+                                                                     int                  worker_fd,
+                                                                     int                  manager_fd,
+                                                                     SECMODModule        *module);
+static void gdm_smartcard_manager_worker_free (GdmSmartcardManagerWorker *worker);
+static gboolean sc_read_bytes (gint fd, gpointer bytes, gsize num_bytes);
+static gboolean sc_write_bytes (gint fd, gconstpointer bytes, gsize num_bytes);
+static GdmSmartcard *sc_read_smartcard (gint fd, SECMODModule *module);
+static gboolean sc_write_smartcard (gint fd, GdmSmartcard *card);
+
+enum {
+        PROP_0 = 0,
+        PROP_MODULE_PATH,
+        NUMBER_OF_PROPERTIES
+};
+
+enum {
+        SMARTCARD_INSERTED = 0,
+        SMARTCARD_REMOVED,
+        ERROR,
+        NUMBER_OF_SIGNALS
+};
+
+static guint gdm_smartcard_manager_signals[NUMBER_OF_SIGNALS];
+
+G_DEFINE_TYPE (GdmSmartcardManager,
+               gdm_smartcard_manager,
+               G_TYPE_OBJECT);
+
+static void
+gdm_smartcard_manager_class_init (GdmSmartcardManagerClass *manager_class)
+{
+        GObjectClass *gobject_class;
+
+        gobject_class = G_OBJECT_CLASS (manager_class);
+
+        gobject_class->finalize = gdm_smartcard_manager_finalize;
+
+        gdm_smartcard_manager_class_install_signals (manager_class);
+        gdm_smartcard_manager_class_install_properties (manager_class);
+
+        g_type_class_add_private (manager_class,
+                                  sizeof (GdmSmartcardManagerPrivate));
+}
+
+static void
+gdm_smartcard_manager_class_install_properties (GdmSmartcardManagerClass *card_class)
+{
+        GObjectClass *object_class;
+        GParamSpec *param_spec;
+
+        object_class = G_OBJECT_CLASS (card_class);
+        object_class->set_property = gdm_smartcard_manager_set_property;
+        object_class->get_property = gdm_smartcard_manager_get_property;
+
+        param_spec = g_param_spec_string ("module-path", _("Module Path"),
+                                          _("path to smartcard PKCS #11 driver"),
+                                          NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+        g_object_class_install_property (object_class, PROP_MODULE_PATH, param_spec);
+}
+
+static void
+gdm_smartcard_manager_set_property (GObject       *object,
+                                    guint          prop_id,
+                                    const GValue  *value,
+                                    GParamSpec    *pspec)
+{
+        GdmSmartcardManager *manager = GDM_SMARTCARD_MANAGER (object);
+
+        switch (prop_id) {
+                case PROP_MODULE_PATH:
+                        gdm_smartcard_manager_set_module_path (manager,
+                                                                   g_value_get_string (value));
+                        break;
+
+                default:
+                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                        break;
+        }
+}
+
+static void
+gdm_smartcard_manager_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+        GdmSmartcardManager *manager = GDM_SMARTCARD_MANAGER (object);
+        char *module_path;
+
+        switch (prop_id) {
+                case PROP_MODULE_PATH:
+                        module_path = gdm_smartcard_manager_get_module_path (manager);
+                        g_value_set_string (value, module_path);
+                        g_free (module_path);
+                        break;
+
+                default:
+                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                        break;
+        }
+}
+
+char *
+gdm_smartcard_manager_get_module_path (GdmSmartcardManager *manager)
+{
+        return manager->priv->module_path;
+}
+
+static void
+gdm_smartcard_manager_set_module_path (GdmSmartcardManager *manager,
+                                       const char          *module_path)
+{
+        if ((manager->priv->module_path == NULL) && (module_path == NULL)) {
+                return;
+        }
+
+        if (((manager->priv->module_path == NULL) ||
+         (module_path == NULL) ||
+         (strcmp (manager->priv->module_path, module_path) != 0))) {
+                g_free (manager->priv->module_path);
+                manager->priv->module_path = g_strdup (module_path);
+                g_object_notify (G_OBJECT (manager), "module-path");
+        }
+}
+
+static void
+gdm_smartcard_manager_card_removed_handler (GdmSmartcardManager *manager,
+                                            GdmSmartcard        *card)
+{
+        g_debug ("informing smartcard of its removal");
+        _gdm_smartcard_set_state (card, GDM_SMARTCARD_STATE_REMOVED);
+        g_debug ("done");
+}
+
+static void
+gdm_smartcard_manager_card_inserted_handler (GdmSmartcardManager *manager,
+                                             GdmSmartcard        *card)
+{
+        g_debug ("informing smartcard of its insertion");
+
+        _gdm_smartcard_set_state (card, GDM_SMARTCARD_STATE_INSERTED);
+        g_debug ("done");
+
+}
+
+static void
+gdm_smartcard_manager_class_install_signals (GdmSmartcardManagerClass *manager_class)
+{
+        GObjectClass *object_class;
+
+        object_class = G_OBJECT_CLASS (manager_class);
+
+        gdm_smartcard_manager_signals[SMARTCARD_INSERTED] =
+                g_signal_new ("smartcard-inserted",
+                              G_OBJECT_CLASS_TYPE (object_class),
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmSmartcardManagerClass,
+                                               smartcard_inserted),
+                              NULL, NULL, g_cclosure_marshal_VOID__POINTER,
+                              G_TYPE_NONE, 1, G_TYPE_POINTER);
+        manager_class->smartcard_inserted = gdm_smartcard_manager_card_inserted_handler;
+
+        gdm_smartcard_manager_signals[SMARTCARD_REMOVED] =
+                g_signal_new ("smartcard-removed",
+                              G_OBJECT_CLASS_TYPE (object_class),
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmSmartcardManagerClass,
+                                               smartcard_removed),
+                              NULL, NULL, g_cclosure_marshal_VOID__POINTER,
+                              G_TYPE_NONE, 1, G_TYPE_POINTER);
+        manager_class->smartcard_removed = gdm_smartcard_manager_card_removed_handler;
+
+        gdm_smartcard_manager_signals[ERROR] =
+                g_signal_new ("error",
+                              G_OBJECT_CLASS_TYPE (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmSmartcardManagerClass, error),
+                              NULL, NULL, g_cclosure_marshal_VOID__POINTER,
+                              G_TYPE_NONE, 1, G_TYPE_POINTER);
+        manager_class->error = NULL;
+}
+
+static gboolean
+sc_slot_id_equal (CK_SLOT_ID *slot_id_1,
+                      CK_SLOT_ID *slot_id_2)
+{
+        g_assert (slot_id_1 != NULL);
+        g_assert (slot_id_2 != NULL);
+
+        return *slot_id_1 == *slot_id_2;
+}
+
+static gboolean
+sc_slot_id_hash (CK_SLOT_ID *slot_id)
+{
+        guint32 upper_bits, lower_bits;
+        gint temp;
+
+        if (sizeof (CK_SLOT_ID) == sizeof (gint)) {
+                return g_int_hash (slot_id);
+        }
+
+        upper_bits = ((*slot_id) >> 31) - 1;
+        lower_bits = (*slot_id) & 0xffffffff;
+
+        /* The upper bits are almost certainly always zero,
+         * so let's degenerate to g_int_hash for the
+         * (very) common case
+         */
+        temp = lower_bits + upper_bits;
+        return upper_bits + g_int_hash (&temp);
+}
+
+static void
+gdm_smartcard_manager_init (GdmSmartcardManager *manager)
+{
+        g_debug ("initializing smartcard manager");
+
+        manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
+                                                     GDM_TYPE_SMARTCARD_MANAGER,
+                                                     GdmSmartcardManagerPrivate);
+        manager->priv->poll_timeout_id = 0;
+        manager->priv->is_unstoppable = FALSE;
+
+        manager->priv->smartcards =
+                g_hash_table_new_full (g_str_hash,
+                                       g_str_equal,
+                                       (GDestroyNotify) g_free,
+                                       (GDestroyNotify) g_object_unref);
+}
+
+static void
+gdm_smartcard_manager_finalize (GObject *object)
+{
+        GdmSmartcardManager *manager;
+        GObjectClass *gobject_class;
+
+        manager = GDM_SMARTCARD_MANAGER (object);
+        gobject_class =
+                G_OBJECT_CLASS (gdm_smartcard_manager_parent_class);
+
+        gdm_smartcard_manager_stop_now (manager);
+
+        g_hash_table_destroy (manager->priv->smartcards);
+        manager->priv->smartcards = NULL;
+
+        gobject_class->finalize (object);
+}
+
+GQuark
+gdm_smartcard_manager_error_quark (void)
+{
+        static GQuark error_quark = 0;
+
+        if (error_quark == 0) {
+                error_quark = g_quark_from_static_string ("gdm-smartcard-manager-error-quark");
+        }
+
+        return error_quark;
+}
+
+GdmSmartcardManager *
+gdm_smartcard_manager_new (const char *module_path)
+{
+        GdmSmartcardManager *instance;
+
+        instance = GDM_SMARTCARD_MANAGER (g_object_new (GDM_TYPE_SMARTCARD_MANAGER,
+                                                        "module-path", module_path,
+                                                        NULL));
+
+        return instance;
+}
+
+static void
+gdm_smartcard_manager_emit_error (GdmSmartcardManager *manager,
+                                  GError              *error)
+{
+        manager->priv->is_unstoppable = TRUE;
+        g_signal_emit (manager, gdm_smartcard_manager_signals[ERROR], 0,
+                       error);
+        manager->priv->is_unstoppable = FALSE;
+}
+
+static void
+gdm_smartcard_manager_emit_smartcard_inserted (GdmSmartcardManager *manager,
+                                               GdmSmartcard        *card)
+{
+        manager->priv->is_unstoppable = TRUE;
+        g_signal_emit (manager, gdm_smartcard_manager_signals[SMARTCARD_INSERTED], 0,
+                       card);
+        manager->priv->is_unstoppable = FALSE;
+}
+
+static void
+gdm_smartcard_manager_emit_smartcard_removed (GdmSmartcardManager *manager,
+                                              GdmSmartcard        *card)
+{
+        manager->priv->is_unstoppable = TRUE;
+        g_signal_emit (manager, gdm_smartcard_manager_signals[SMARTCARD_REMOVED], 0,
+                       card);
+        manager->priv->is_unstoppable = FALSE;
+}
+
+static gboolean
+gdm_smartcard_manager_check_for_and_process_events (GIOChannel          *io_channel,
+                                                    GIOCondition         condition,
+                                                    GdmSmartcardManagerWorker *worker)
+{
+        GdmSmartcard *card;
+        GdmSmartcardManager *manager;
+        gboolean should_stop;
+        guchar event_type;
+        char *card_name;
+        gint fd;
+
+        manager = worker->manager;
+
+        g_debug ("event!");
+        card = NULL;
+        should_stop = (condition & G_IO_HUP) || (condition & G_IO_ERR);
+
+        if (should_stop) {
+                g_debug ("received %s on event socket, stopping "
+                          "manager...",
+                          (condition & G_IO_HUP) && (condition & G_IO_ERR)?
+                          "error and hangup" :
+                          (condition & G_IO_HUP)?
+                          "hangup" : "error");
+        }
+
+        if (!(condition & G_IO_IN)) {
+                g_debug ("nevermind outta here!");
+                goto out;
+        }
+
+        fd = g_io_channel_unix_get_fd (io_channel);
+
+        event_type = '\0';
+        if (!sc_read_bytes (fd, &event_type, 1)) {
+                g_debug ("could not read event type, stopping");
+                should_stop = TRUE;
+                goto out;
+        }
+
+        card = sc_read_smartcard (fd, worker->module);
+
+        if (card == NULL) {
+                g_debug ("could not read card, stopping");
+                should_stop = TRUE;
+                goto out;
+        }
+
+        card_name = gdm_smartcard_get_name (card);
+        g_debug ("card '%s' had event %c", card_name, event_type);
+
+        switch (event_type) {
+                case 'I':
+                        g_hash_table_replace (manager->priv->smartcards,
+                                              card_name, card);
+                        card_name = NULL;
+
+                        gdm_smartcard_manager_emit_smartcard_inserted (manager, card);
+                        card = NULL;
+                        break;
+
+                case 'R':
+                        gdm_smartcard_manager_emit_smartcard_removed (manager, card);
+                        if (!g_hash_table_remove (manager->priv->smartcards, card_name)) {
+                                g_debug ("got removal event of unknown card!");
+                        }
+                        g_free (card_name);
+                        card_name = NULL;
+                        card = NULL;
+                        break;
+
+                default:
+                        g_free (card_name);
+                        card_name = NULL;
+                        g_object_unref (card);
+
+                        should_stop = TRUE;
+                        break;
+        }
+
+out:
+        if (should_stop) {
+                GError *error;
+
+                error = g_error_new (GDM_SMARTCARD_MANAGER_ERROR,
+                                     GDM_SMARTCARD_MANAGER_ERROR_WATCHING_FOR_EVENTS,
+                                     "%s", (condition & G_IO_IN) ? g_strerror (errno) : _("received error or 
hang up from event source"));
+
+                gdm_smartcard_manager_emit_error (manager, error);
+                g_error_free (error);
+                gdm_smartcard_manager_stop_now (manager);
+                return FALSE;
+        }
+
+        return TRUE;
+}
+
+static void
+stop_manager (GdmSmartcardManager *manager)
+{
+        manager->priv->state = GDM_SMARTCARD_MANAGER_STATE_STOPPED;
+
+        if (manager->priv->nss_is_loaded) {
+                NSS_Shutdown ();
+                manager->priv->nss_is_loaded = FALSE;
+        }
+        g_debug ("smartcard manager stopped");
+}
+
+static void
+stop_worker (GdmSmartcardManagerWorker *worker)
+{
+        GdmSmartcardManager *manager;
+
+        manager = worker->manager;
+
+        if (worker->event_source != NULL) {
+                g_source_destroy (worker->event_source);
+                worker->event_source = NULL;
+        }
+
+        if (worker->thread != NULL) {
+                SECMOD_CancelWait (worker->module);
+                worker->thread = NULL;
+        }
+
+        SECMOD_DestroyModule (worker->module);
+        manager->priv->workers = g_list_remove (manager->priv->workers, worker);
+
+        if (manager->priv->workers == NULL && manager->priv->state != GDM_SMARTCARD_MANAGER_STATE_STOPPED) {
+                stop_manager (manager);
+        }
+}
+
+static void
+gdm_smartcard_manager_event_processing_stopped_handler (GdmSmartcardManagerWorker *worker)
+{
+        worker->event_source = NULL;
+
+        stop_worker (worker);
+}
+
+static void
+gdm_smartcard_manager_stop_watching_for_events (GdmSmartcardManager  *manager)
+{
+        GList *node;
+
+        node = manager->priv->workers;
+        while (node != NULL) {
+                GdmSmartcardManagerWorker *worker;
+                GList *next_node;
+
+                worker = (GdmSmartcardManagerWorker *) node->data;
+                next_node = node->next;
+
+                stop_worker (worker);
+
+                node = next_node;
+        }
+}
+
+static gboolean
+sc_load_nss (GError **error)
+{
+        SECStatus status = SECSuccess;
+        static const guint32 flags =
+        NSS_INIT_READONLY |
+        NSS_INIT_FORCEOPEN | NSS_INIT_NOROOTINIT |
+        NSS_INIT_OPTIMIZESPACE | NSS_INIT_PK11RELOAD;
+
+        g_debug ("attempting to load NSS database '%s'",
+                 GDM_SMARTCARD_MANAGER_NSS_DB);
+
+        PR_Init (PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+        status = NSS_Initialize (GDM_SMARTCARD_MANAGER_NSS_DB,
+                                 "", "", SECMOD_DB, flags);
+
+        if (status != SECSuccess) {
+                gsize error_message_size;
+                char *error_message;
+
+                error_message_size = PR_GetErrorTextLength ();
+
+                if (error_message_size == 0) {
+                        g_debug ("NSS security system could not be initialized");
+                        g_set_error (error,
+                                     GDM_SMARTCARD_MANAGER_ERROR,
+                                     GDM_SMARTCARD_MANAGER_ERROR_WITH_NSS,
+                                     _("NSS security system could not be initialized"));
+                        goto out;
+                }
+
+                error_message = g_slice_alloc0 (error_message_size);
+                PR_GetErrorText (error_message);
+
+                g_set_error (error,
+                             GDM_SMARTCARD_MANAGER_ERROR,
+                             GDM_SMARTCARD_MANAGER_ERROR_WITH_NSS,
+                             "%s", error_message);
+                g_debug ("NSS security system could not be initialized - %s",
+                          error_message);
+
+                g_slice_free1 (error_message_size, error_message);
+
+                goto out;
+        }
+
+        g_debug ("NSS database sucessfully loaded");
+        return TRUE;
+
+out:
+        g_debug ("NSS database couldn't be sucessfully loaded");
+        return FALSE;
+}
+
+static GList *
+get_available_modules (GdmSmartcardManager  *manager)
+{
+        SECMODModuleList *module_list, *tmp;
+        GList *modules;
+
+        g_debug ("Getting list of suitable modules");
+
+        module_list = SECMOD_GetDefaultModuleList ();
+        modules = NULL;
+        for (tmp = module_list; tmp != NULL; tmp = tmp->next) {
+                if (!SECMOD_HasRemovableSlots (tmp->module) ||
+                    !tmp->module->loaded)
+                        continue;
+
+                g_debug ("Using module '%s'", tmp->module->commonName);
+
+                modules = g_list_prepend (modules,
+                                          SECMOD_ReferenceModule (tmp->module));
+        }
+
+        return modules;
+}
+
+static gboolean
+load_driver (GdmSmartcardManager  *manager,
+             char                 *module_path,
+             GError              **error)
+{
+        GList *modules;
+        char *module_spec;
+        gboolean module_explicitly_specified;
+
+        g_debug ("attempting to load driver...");
+
+        modules = NULL;
+        module_explicitly_specified = module_path != NULL;
+        if (module_explicitly_specified) {
+                SECMODModule *module;
+
+                module_spec = g_strdup_printf ("library=\"%s\"", module_path);
+                g_debug ("loading smartcard driver using spec '%s'",
+                          module_spec);
+
+                module = SECMOD_LoadUserModule (module_spec,
+                                                NULL /* parent */,
+                                                FALSE /* recurse */);
+                g_free (module_spec);
+                module_spec = NULL;
+
+                if (!SECMOD_HasRemovableSlots (module) ||
+                    !module->loaded) {
+                        modules = g_list_prepend (modules, module);
+                } else {
+                        g_debug ("fallback module found but not %s",
+                                 SECMOD_HasRemovableSlots (module)?
+                                 "removable" : "loaded");
+                        SECMOD_DestroyModule (module);
+                }
+
+        } else {
+                SECMODListLock *lock;
+
+                lock = SECMOD_GetDefaultModuleListLock ();
+
+                if (lock != NULL) {
+                        SECMOD_GetReadLock (lock);
+                        modules = get_available_modules (manager);
+                        SECMOD_ReleaseReadLock (lock);
+                }
+
+                /* fallback to compiled in driver path
+                 */
+                if (modules == NULL) {
+                        SECMODModule *module;
+                        module_path = GDM_SMARTCARD_MANAGER_DRIVER;
+                        module_spec = g_strdup_printf ("library=\"%s\"", module_path);
+                        g_debug ("loading smartcard driver using spec '%s'",
+                                module_spec);
+
+                        module = SECMOD_LoadUserModule (module_spec,
+                                NULL /* parent */,
+                                FALSE /* recurse */);
+                        g_free (module_spec);
+                        module_spec = NULL;
+
+                        if (!SECMOD_HasRemovableSlots (module) ||
+                            !module->loaded) {
+                                modules = g_list_prepend (modules, module);
+                        } else {
+                                g_debug ("fallback module found but not loaded");
+                                SECMOD_DestroyModule (module);
+                        }
+                }
+
+        }
+
+        if (!module_explicitly_specified && modules == NULL) {
+                g_set_error (error,
+                             GDM_SMARTCARD_MANAGER_ERROR,
+                             GDM_SMARTCARD_MANAGER_ERROR_LOADING_DRIVER,
+                             _("no suitable smartcard driver could be found"));
+        } else if (modules == NULL) {
+
+                gsize error_message_size;
+                char *error_message;
+
+                error_message_size = PR_GetErrorTextLength ();
+
+                if (error_message_size == 0) {
+                        g_debug ("smartcard driver '%s' could not be loaded",
+                                  module_path);
+                        g_set_error (error,
+                                     GDM_SMARTCARD_MANAGER_ERROR,
+                                     GDM_SMARTCARD_MANAGER_ERROR_LOADING_DRIVER,
+                                     _("smartcard driver '%s' could not be "
+                                       "loaded"), module_path);
+                        goto out;
+                }
+
+                error_message = g_slice_alloc0 (error_message_size);
+                PR_GetErrorText (error_message);
+
+                g_set_error (error,
+                             GDM_SMARTCARD_MANAGER_ERROR,
+                             GDM_SMARTCARD_MANAGER_ERROR_LOADING_DRIVER,
+                             "%s", error_message);
+
+                g_debug ("smartcard driver '%s' could not be loaded - %s",
+                          module_path, error_message);
+                g_slice_free1 (error_message_size, error_message);
+        }
+
+        manager->priv->modules = modules;
+out:
+        return manager->priv->modules != NULL;
+}
+
+static void
+gdm_smartcard_manager_get_all_cards (GdmSmartcardManager *manager)
+{
+        GList *node;
+        int i;
+
+        node = manager->priv->workers;
+        while (node != NULL) {
+
+                GdmSmartcardManagerWorker *worker;
+
+                worker = (GdmSmartcardManagerWorker *) node->data;
+
+                for (i = 0; i < worker->module->slotCount; i++) {
+                        GdmSmartcard *card;
+                        CK_SLOT_ID    slot_id;
+                        gint          slot_series;
+                        char         *card_name;
+
+                        slot_id = PK11_GetSlotID (worker->module->slots[i]);
+                        slot_series = PK11_GetSlotSeries (worker->module->slots[i]);
+
+                        card = _gdm_smartcard_new (worker->module,
+                                                   slot_id, slot_series);
+
+                        card_name = gdm_smartcard_get_name (card);
+
+                        g_hash_table_replace (manager->priv->smartcards,
+                                              card_name, card);
+                }
+                node = node->next;
+        }
+}
+
+static GdmSmartcardManagerWorker *
+start_worker (GdmSmartcardManager  *manager,
+              SECMODModule         *module,
+              GError              **error)
+{
+        GIOChannel *io_channel;
+        GSource *source;
+        GdmSmartcardManagerWorker *worker;
+
+        worker = gdm_smartcard_manager_create_worker (manager, module);
+
+        if (worker == NULL) {
+                g_set_error (error,
+                             GDM_SMARTCARD_MANAGER_ERROR,
+                             GDM_SMARTCARD_MANAGER_ERROR_WATCHING_FOR_EVENTS,
+                             _("could not watch for incoming card events - %s"),
+                             g_strerror (errno));
+
+                goto out;
+        }
+
+        io_channel = g_io_channel_unix_new (worker->manager_fd);
+
+        source = g_io_create_watch (io_channel, G_IO_IN | G_IO_HUP);
+        g_io_channel_unref (io_channel);
+        io_channel = NULL;
+
+        worker->event_source = source;
+
+        g_source_set_callback (worker->event_source,
+                               (GSourceFunc) (GIOFunc)
+                               gdm_smartcard_manager_check_for_and_process_events,
+                               worker,
+                               (GDestroyNotify)
+                               gdm_smartcard_manager_event_processing_stopped_handler);
+        g_source_attach (worker->event_source, NULL);
+        g_source_unref (worker->event_source);
+out:
+        return worker;
+}
+
+static void
+start_workers (GdmSmartcardManager *manager)
+{
+        GList        *node;
+
+        node = manager->priv->modules;
+        while (node != NULL) {
+                SECMODModule *module;
+                GdmSmartcardManagerWorker *worker;
+                GError *error;
+
+                module = (SECMODModule *) node->data;
+
+                error = NULL;
+                worker = start_worker (manager, module, &error);
+                if (worker == NULL) {
+                        g_warning ("%s", error->message);
+                        g_error_free (error);
+                } else {
+                        manager->priv->workers = g_list_prepend (manager->priv->workers,
+                                                                 worker);
+                }
+                node = node->next;
+        }
+}
+
+gboolean
+gdm_smartcard_manager_start (GdmSmartcardManager  *manager,
+                             GError              **error)
+{
+        GError *nss_error;
+
+        if (manager->priv->state == GDM_SMARTCARD_MANAGER_STATE_STARTED) {
+                g_debug ("smartcard manager already started");
+                return TRUE;
+        }
+
+        manager->priv->state = GDM_SMARTCARD_MANAGER_STATE_STARTING;
+
+        nss_error = NULL;
+        if (!manager->priv->nss_is_loaded && !sc_load_nss (&nss_error)) {
+                g_propagate_error (error, nss_error);
+                goto out;
+        }
+        manager->priv->nss_is_loaded = TRUE;
+
+        if (manager->priv->modules == NULL) {
+                if (!load_driver (manager, manager->priv->module_path, &nss_error)) {
+                        g_propagate_error (error, nss_error);
+                        goto out;
+                }
+        }
+
+        start_workers (manager);
+
+        /* populate the hash with cards that are already inserted
+         */
+        gdm_smartcard_manager_get_all_cards (manager);
+
+        manager->priv->state = GDM_SMARTCARD_MANAGER_STATE_STARTED;
+
+out:
+        /* don't leave it in a half started state
+         */
+        if (manager->priv->state != GDM_SMARTCARD_MANAGER_STATE_STARTED) {
+                g_debug ("smartcard manager could not be completely started");
+                gdm_smartcard_manager_stop (manager);
+        } else {
+                g_debug ("smartcard manager started");
+        }
+
+        return manager->priv->state == GDM_SMARTCARD_MANAGER_STATE_STARTED;
+}
+
+static gboolean
+gdm_smartcard_manager_stop_now (GdmSmartcardManager *manager)
+{
+        if (manager->priv->state == GDM_SMARTCARD_MANAGER_STATE_STOPPED) {
+                return FALSE;
+        }
+
+        gdm_smartcard_manager_stop_watching_for_events (manager);
+
+        return FALSE;
+}
+
+static void
+gdm_smartcard_manager_queue_stop (GdmSmartcardManager *manager)
+{
+
+        manager->priv->state = GDM_SMARTCARD_MANAGER_STATE_STOPPING;
+
+        g_idle_add ((GSourceFunc) gdm_smartcard_manager_stop_now, manager);
+}
+
+void
+gdm_smartcard_manager_stop (GdmSmartcardManager *manager)
+{
+        if (manager->priv->state == GDM_SMARTCARD_MANAGER_STATE_STOPPED) {
+                return;
+        }
+
+        if (manager->priv->is_unstoppable) {
+                gdm_smartcard_manager_queue_stop (manager);
+                return;
+        }
+
+        gdm_smartcard_manager_stop_now (manager);
+}
+
+static GdmSmartcardManagerWorker *
+gdm_smartcard_manager_worker_new (GdmSmartcardManager *manager,
+                                  gint                 worker_fd,
+                                  gint                 manager_fd,
+                                  SECMODModule        *module)
+{
+        GdmSmartcardManagerWorker *worker;
+
+        worker = g_slice_new0 (GdmSmartcardManagerWorker);
+        worker->manager = manager;
+        worker->fd = worker_fd;
+        worker->manager_fd = manager_fd;
+        worker->module = module;
+
+        worker->smartcards =
+                g_hash_table_new_full ((GHashFunc) sc_slot_id_hash,
+                                       (GEqualFunc) sc_slot_id_equal,
+                                       (GDestroyNotify) g_free,
+                                       (GDestroyNotify) g_object_unref);
+
+        return worker;
+}
+
+static void
+gdm_smartcard_manager_worker_free (GdmSmartcardManagerWorker *worker)
+{
+        if (worker->smartcards != NULL) {
+                g_hash_table_destroy (worker->smartcards);
+                worker->smartcards = NULL;
+        }
+
+        g_slice_free (GdmSmartcardManagerWorker, worker);
+}
+
+static gboolean
+sc_read_bytes (gint fd, gpointer bytes, gsize num_bytes)
+{
+        size_t bytes_left;
+        size_t total_bytes_read;
+        ssize_t bytes_read;
+
+        bytes_left = (size_t) num_bytes;
+        total_bytes_read = 0;
+
+        do {
+                bytes_read = read (fd, (gchar *) bytes + total_bytes_read, bytes_left);
+                g_assert (bytes_read <= (ssize_t) bytes_left);
+
+                if (bytes_read <= 0) {
+                        if ((bytes_read < 0) && (errno == EINTR || errno == EAGAIN)) {
+                                continue;
+                        }
+
+                        bytes_left = 0;
+                } else {
+                        bytes_left -= bytes_read;
+                        total_bytes_read += bytes_read;
+                }
+        } while (bytes_left > 0);
+
+        if (total_bytes_read <  (size_t) num_bytes) {
+                return FALSE;
+        }
+
+        return TRUE;
+}
+
+static gboolean
+sc_write_bytes (gint fd, gconstpointer bytes, gsize num_bytes)
+{
+        size_t bytes_left;
+        size_t total_bytes_written;
+        ssize_t bytes_written;
+
+        bytes_left = (size_t) num_bytes;
+        total_bytes_written = 0;
+
+        do {
+                bytes_written = write (fd, (gchar *) bytes + total_bytes_written, bytes_left);
+                g_assert (bytes_written <= (ssize_t) bytes_left);
+
+                if (bytes_written <= 0) {
+                        if ((bytes_written < 0) && (errno == EINTR || errno == EAGAIN)) {
+                                continue;
+                        }
+
+                        bytes_left = 0;
+                } else {
+                        bytes_left -= bytes_written;
+                        total_bytes_written += bytes_written;
+                }
+        } while (bytes_left > 0);
+
+        if (total_bytes_written <  (size_t) num_bytes) {
+                return FALSE;
+        }
+
+        return TRUE;
+}
+
+static GdmSmartcard *
+sc_read_smartcard (gint          fd,
+                   SECMODModule *module)
+{
+        GdmSmartcard *card;
+        char *card_name;
+        gsize card_name_size;
+
+        card_name_size = 0;
+        if (!sc_read_bytes (fd, &card_name_size, sizeof (card_name_size))) {
+                return NULL;
+        }
+
+        card_name = g_slice_alloc0 (card_name_size);
+        if (!sc_read_bytes (fd, card_name, card_name_size)) {
+                g_slice_free1 (card_name_size, card_name);
+                return NULL;
+        }
+        card = _gdm_smartcard_new_from_name (module, card_name);
+        g_slice_free1 (card_name_size, card_name);
+
+        return card;
+}
+
+static gboolean
+sc_write_smartcard (gint          fd,
+                    GdmSmartcard *card)
+{
+        gsize card_name_size;
+        char *card_name;
+
+        card_name = gdm_smartcard_get_name (card);
+        card_name_size = strlen (card_name) + 1;
+
+        if (!sc_write_bytes (fd, &card_name_size, sizeof (card_name_size))) {
+                g_free (card_name);
+                return FALSE;
+        }
+
+        if (!sc_write_bytes (fd, card_name, card_name_size)) {
+                g_free (card_name);
+                return FALSE;
+        }
+        g_free (card_name);
+
+        return TRUE;
+}
+
+static gboolean
+gdm_smartcard_manager_worker_emit_smartcard_removed (GdmSmartcardManagerWorker  *worker,
+                                                     GdmSmartcard               *card,
+                                                     GError                    **error)
+{
+        g_debug ("card '%s' removed!", gdm_smartcard_get_name (card));
+
+        if (!sc_write_bytes (worker->fd, "R", 1)) {
+                goto error_out;
+        }
+
+        if (!sc_write_smartcard (worker->fd, card)) {
+                goto error_out;
+        }
+
+        return TRUE;
+
+error_out:
+        g_set_error (error, GDM_SMARTCARD_MANAGER_ERROR,
+                     GDM_SMARTCARD_MANAGER_ERROR_REPORTING_EVENTS,
+                     "%s", g_strerror (errno));
+        return FALSE;
+}
+
+static gboolean
+gdm_smartcard_manager_worker_emit_smartcard_inserted (GdmSmartcardManagerWorker  *worker,
+                                                      GdmSmartcard               *card,
+                                                      GError                    **error)
+{
+
+        g_debug ("card '%s' inserted!", gdm_smartcard_get_name (card));
+        if (!sc_write_bytes (worker->fd, "I", 1)) {
+                goto error_out;
+        }
+
+        if (!sc_write_smartcard (worker->fd, card)) {
+                goto error_out;
+        }
+
+        return TRUE;
+
+error_out:
+        g_set_error (error, GDM_SMARTCARD_MANAGER_ERROR,
+                     GDM_SMARTCARD_MANAGER_ERROR_REPORTING_EVENTS,
+                     "%s", g_strerror (errno));
+        return FALSE;
+}
+
+static gboolean
+gdm_smartcard_manager_worker_watch_for_and_process_event (GdmSmartcardManagerWorker  *worker,
+                                                          GError                    **error)
+{
+        PK11SlotInfo *slot;
+        CK_SLOT_ID slot_id, *key;
+        gint slot_series, card_slot_series;
+        GdmSmartcard *card;
+        GError *processing_error;
+
+        g_debug ("waiting for card event");
+
+        /* FIXME: we return FALSE quite a bit in this function without cleaning up
+         * resources.  By returning FALSE we're going to ultimately exit anyway, but
+         * we should still be tidier about things.
+         */
+
+        slot = SECMOD_WaitForAnyTokenEvent (worker->module, 0, PR_SecondsToInterval (1));
+
+        processing_error = NULL;
+
+        if (slot == NULL) {
+                int error_code;
+
+                error_code = PORT_GetError ();
+                if ((error_code == 0) || (error_code == SEC_ERROR_NO_EVENT)) {
+                        g_debug ("spurrious event occurred");
+                        return TRUE;
+                }
+
+                /* FIXME: is there a function to convert from a PORT error
+                 * code to a translated string?
+                 */
+                g_set_error (error, GDM_SMARTCARD_MANAGER_ERROR,
+                             GDM_SMARTCARD_MANAGER_ERROR_WITH_NSS,
+                             _("encountered unexpected error while "
+                               "waiting for smartcard events"));
+                return FALSE;
+        }
+
+        /* the slot id and series together uniquely identify a card.
+         * You can never have two cards with the same slot id at the
+         * same time, however (I think), so we can key off of it.
+         */
+        slot_id = PK11_GetSlotID (slot);
+        slot_series = PK11_GetSlotSeries (slot);
+
+        /* First check to see if there is a card that we're currently
+         * tracking in the slot.
+         */
+        key = g_new (CK_SLOT_ID, 1);
+        *key = slot_id;
+        card = g_hash_table_lookup (worker->smartcards, key);
+
+        if (card != NULL) {
+                card_slot_series = gdm_smartcard_get_slot_series (card);
+        } else {
+                card_slot_series = -1;
+        }
+
+        if (PK11_IsPresent (slot)) {
+                /* Now, check to see if their is a new card in the slot.
+                 * If there was a different card in the slot now than
+                 * there was before, then we need to emit a removed signal
+                 * for the old card (we don't want unpaired insertion events).
+                 */
+                if ((card != NULL) &&
+                    card_slot_series != slot_series) {
+                        if (!gdm_smartcard_manager_worker_emit_smartcard_removed (worker, card, 
&processing_error)) {
+                                g_propagate_error (error, processing_error);
+                                return FALSE;
+                        }
+                }
+
+                card = _gdm_smartcard_new (worker->module,
+                                           slot_id, slot_series);
+
+                g_hash_table_replace (worker->smartcards,
+                                      key, card);
+                key = NULL;
+
+                if (!gdm_smartcard_manager_worker_emit_smartcard_inserted (worker, card, &processing_error)) 
{
+                        g_propagate_error (error, processing_error);
+                        return FALSE;
+                }
+        } else {
+                /* if we aren't tracking the card, just discard the event.
+                 * We don't want unpaired remove events.  Note on startup
+                 * NSS will generate an "insertion" event if a card is
+                 * already inserted in the slot.
+                 */
+                if ((card != NULL)) {
+                        /* FIXME: i'm not sure about this code.  Maybe we
+                         * shouldn't do this at all, or maybe we should do it
+                         * n times (where n = slot_series - card_slot_series + 1)
+                         *
+                         * Right now, i'm just doing it once.
+                         */
+                        if ((slot_series - card_slot_series) > 1) {
+
+                                if (!gdm_smartcard_manager_worker_emit_smartcard_removed (worker, card, 
&processing_error)) {
+                                        g_propagate_error (error, processing_error);
+                                        return FALSE;
+                                }
+                                g_hash_table_remove (worker->smartcards, key);
+
+                                card = _gdm_smartcard_new (worker->module,
+                                                                slot_id, slot_series);
+                                g_hash_table_replace (worker->smartcards,
+                                                      key, card);
+                                key = NULL;
+                                if (!gdm_smartcard_manager_worker_emit_smartcard_inserted (worker, card, 
&processing_error)) {
+                                        g_propagate_error (error, processing_error);
+                                        return FALSE;
+                                }
+                        }
+
+                        if (!gdm_smartcard_manager_worker_emit_smartcard_removed (worker, card, 
&processing_error)) {
+                                g_propagate_error (error, processing_error);
+                                return FALSE;
+                        }
+
+                        g_hash_table_remove (worker->smartcards, key);
+                        card = NULL;
+                } else {
+                        g_debug ("got spurious remove event");
+                }
+        }
+
+        g_free (key);
+        PK11_FreeSlot (slot);
+
+        return TRUE;
+}
+
+static void
+gdm_smartcard_manager_worker_run (GdmSmartcardManagerWorker *worker)
+{
+        GError *error;
+        gboolean should_continue;
+
+        do
+        {
+                error = NULL;
+                should_continue = gdm_smartcard_manager_worker_watch_for_and_process_event (worker, &error);
+        }
+        while (should_continue);
+
+        if (error != NULL)  {
+                g_debug ("could not process card event - %s", error->message);
+                g_error_free (error);
+        }
+
+        gdm_smartcard_manager_worker_free (worker);
+}
+
+static GdmSmartcardManagerWorker *
+gdm_smartcard_manager_create_worker (GdmSmartcardManager  *manager,
+                                     SECMODModule         *module)
+{
+        GdmSmartcardManagerWorker *worker;
+       gint pipefds[2];
+
+        if (!g_unix_open_pipe (pipefds, FD_CLOEXEC, NULL)) {
+                return FALSE;
+        }
+
+        worker = gdm_smartcard_manager_worker_new (manager,
+                                                   pipefds[1],
+                                                   pipefds[0],
+                                                   module);
+
+        worker->thread = g_thread_new ("smartcard",
+                                      (GThreadFunc) gdm_smartcard_manager_worker_run,
+                                      worker);
+
+        if (worker->thread == NULL) {
+                gdm_smartcard_manager_worker_free (worker);
+                return NULL;
+        }
+
+        return worker;
+}
+
+#ifdef GDM_SMARTCARD_MANAGER_ENABLE_TEST
+#include <glib.h>
+
+static GMainLoop *event_loop;
+static gboolean should_exit_on_next_remove = FALSE;
+
+static gboolean
+on_timeout (GdmSmartcardManager *manager)
+{
+        GError *error = NULL;
+        g_print ("Re-enabling manager.\n");
+
+        if (!gdm_smartcard_manager_start (manager, &error)) {
+                g_warning ("could not start smartcard manager - %s",
+                           error->message);
+                g_error_free (error);
+                return 1;
+        }
+        g_print ("Please re-insert smartcard\n");
+
+        should_exit_on_next_remove = TRUE;
+
+        return FALSE;
+}
+
+static void
+on_device_inserted (GdmSmartcardManager *manager,
+                    GdmSmartcard        *card)
+{
+        g_print ("smartcard inserted!\n");
+        g_print ("Please remove it.\n");
+}
+
+static void
+on_device_removed (GdmSmartcardManager *manager,
+                   GdmSmartcard        *card)
+{
+        g_print ("smartcard removed!\n");
+
+        if (should_exit_on_next_remove) {
+                g_main_loop_quit (event_loop);
+        } else {
+                g_print ("disabling manager for 2 seconds\n");
+                gdm_smartcard_manager_stop (manager);
+                g_timeout_add (2000, (GSourceFunc) on_timeout, manager);
+        }
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+        GdmSmartcardManager *manager;
+        GError *error;
+
+        g_log_set_always_fatal (G_LOG_LEVEL_ERROR
+                                | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING);
+
+        g_message ("creating instance of 'smartcard manager' object...");
+        manager = gdm_smartcard_manager_new (NULL);
+        g_message ("'smartcard manager' object created successfully");
+
+        g_signal_connect (manager, "smartcard-inserted",
+                          G_CALLBACK (on_device_inserted), NULL);
+
+        g_signal_connect (manager, "smartcard-removed",
+                          G_CALLBACK (on_device_removed), NULL);
+
+        g_message ("starting listener...");
+
+        error = NULL;
+        if (!gdm_smartcard_manager_start (manager, &error)) {
+                g_warning ("could not start smartcard manager - %s",
+                           error->message);
+                g_error_free (error);
+                return 1;
+        }
+
+        event_loop = g_main_loop_new (NULL, FALSE);
+        g_main_loop_run (event_loop);
+        g_main_loop_unref (event_loop);
+        event_loop = NULL;
+
+        g_message ("destroying previously created 'smartcard manager' object...");
+        g_object_unref (manager);
+        manager = NULL;
+        g_message ("'smartcard manager' object destroyed successfully");
+
+        return 0;
+}
+#endif
diff --git a/gui/simple-greeter/extensions/smartcard/gdm-smartcard-manager.h 
b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-manager.h
new file mode 100644
index 0000000..38e13c3
--- /dev/null
+++ b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-manager.h
@@ -0,0 +1,86 @@
+/* gdm-smartcard-manager.h - object for monitoring smartcard insertion and
+ *                           removal events
+ *
+ * Copyright (C) 2006, 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * Written by: Ray Strode
+ */
+#ifndef GDM_SMARTCARD_MANAGER_H
+#define GDM_SMARTCARD_MANAGER_H
+
+#define GDM_SMARTCARD_ENABLE_INTERNAL_API
+#include "gdm-smartcard.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+#define GDM_TYPE_SMARTCARD_MANAGER            (gdm_smartcard_manager_get_type ())
+#define GDM_SMARTCARD_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GDM_TYPE_SMARTCARD_MANAGER, GdmSmartcardManager))
+#define GDM_SMARTCARD_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_SMARTCARD_MANAGER, 
GdmSmartcardManagerClass))
+#define GDM_IS_SMARTCARD_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SC_TYPE_SMARTCARD_MANAGER))
+#define GDM_IS_SMARTCARD_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SC_TYPE_SMARTCARD_MANAGER))
+#define GDM_SMARTCARD_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GDM_TYPE_SMARTCARD_MANAGER, 
GdmSmartcardManagerClass))
+#define GDM_SMARTCARD_MANAGER_ERROR           (gdm_smartcard_manager_error_quark ())
+typedef struct _GdmSmartcardManager GdmSmartcardManager;
+typedef struct _GdmSmartcardManagerClass GdmSmartcardManagerClass;
+typedef struct _GdmSmartcardManagerPrivate GdmSmartcardManagerPrivate;
+typedef enum _GdmSmartcardManagerError GdmSmartcardManagerError;
+
+struct _GdmSmartcardManager {
+    GObject parent;
+
+    /*< private > */
+    GdmSmartcardManagerPrivate *priv;
+};
+
+struct _GdmSmartcardManagerClass {
+        GObjectClass parent_class;
+
+        /* Signals */
+        void (*smartcard_inserted) (GdmSmartcardManager *manager,
+                                    GdmSmartcard        *token);
+        void (*smartcard_removed) (GdmSmartcardManager *manager,
+                                   GdmSmartcard        *token);
+        void (*error) (GdmSmartcardManager *manager,
+                       GError              *error);
+};
+
+enum _GdmSmartcardManagerError {
+    GDM_SMARTCARD_MANAGER_ERROR_GENERIC = 0,
+    GDM_SMARTCARD_MANAGER_ERROR_WITH_NSS,
+    GDM_SMARTCARD_MANAGER_ERROR_LOADING_DRIVER,
+    GDM_SMARTCARD_MANAGER_ERROR_WATCHING_FOR_EVENTS,
+    GDM_SMARTCARD_MANAGER_ERROR_REPORTING_EVENTS
+};
+
+GType gdm_smartcard_manager_get_type (void) G_GNUC_CONST;
+GQuark gdm_smartcard_manager_error_quark (void) G_GNUC_CONST;
+
+GdmSmartcardManager *gdm_smartcard_manager_new (const char *module);
+
+gboolean gdm_smartcard_manager_start (GdmSmartcardManager  *manager,
+                                      GError              **error);
+
+void gdm_smartcard_manager_stop (GdmSmartcardManager *manager);
+
+char *gdm_smartcard_manager_get_module_path (GdmSmartcardManager *manager);
+gboolean gdm_smartcard_manager_login_token_is_inserted (GdmSmartcardManager *manager);
+
+G_END_DECLS
+#endif                                /* GDM_SMARTCARD_MANAGER_H */
diff --git a/gui/simple-greeter/extensions/smartcard/gdm-smartcard-worker.c 
b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-worker.c
new file mode 100644
index 0000000..711c2c7
--- /dev/null
+++ b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-worker.c
@@ -0,0 +1,184 @@
+#include "config.h"
+
+#include <fcntl.h>
+#include <locale.h>
+#include <sys/prctl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <glib.h>
+
+#include "gdm-smartcard-manager.h"
+#include "gdm-smartcard.h"
+
+#ifndef GDM_SMARTCARDS_CONF
+#define GDM_SMARTCARDS_CONF GDMCONFDIR "/smartcards.conf"
+#endif
+
+#ifndef GDM_SMARTCARDS_GROUP
+#define GDM_SMARTCARDS_GROUP "Smartcards"
+#endif
+
+#ifndef GDM_SMARTCARDS_KEY_ENABLED
+#define GDM_SMARTCARDS_KEY_ENABLED "Enabled"
+#endif
+
+#ifndef GDM_SMARTCARDS_KEY_DRIVER
+#define GDM_SMARTCARDS_KEY_DRIVER "Driver"
+#endif
+
+static GMainLoop *event_loop;
+static GdmSmartcardManager *manager;
+static int signal_pipe_fds[2] = { -1, -1 };
+
+static void
+on_smartcard_event (const char *event_string)
+{
+        g_debug ("smartcard event '%s' happened", event_string);
+        g_print ("%s", event_string);
+        fflush (stdout);
+}
+
+static void
+watch_for_smartcards (void)
+{
+        GError *error;
+        char *driver;
+        GKeyFile *cfg;
+
+        cfg = g_key_file_new ();
+
+        error = NULL;
+        driver = NULL;
+        if (g_key_file_load_from_file (cfg, GDM_SMARTCARDS_CONF, G_KEY_FILE_NONE, &error)) {
+                if (!g_key_file_get_boolean (cfg, GDM_SMARTCARDS_GROUP, GDM_SMARTCARDS_KEY_ENABLED, &error)) 
{
+                        g_debug ("smartcard support is not enabled");
+                        goto out;
+                }
+
+                driver = g_key_file_get_string (cfg, GDM_SMARTCARDS_GROUP, GDM_SMARTCARDS_KEY_DRIVER, NULL);
+                g_debug ("smartcards driver is set to '%s'",
+                        driver == NULL || driver[0] == '\0'? "<automatic>" : driver);
+        }
+
+        g_debug ("watching for smartcard insertion and removal events");
+        manager = gdm_smartcard_manager_new (driver);
+        g_free (driver);
+
+        g_signal_connect_swapped (manager,
+                                  "smartcard-inserted",
+                                  G_CALLBACK (on_smartcard_event),
+                                  "I");
+
+        g_signal_connect_swapped (manager,
+                                  "smartcard-removed",
+                                  G_CALLBACK (on_smartcard_event),
+                                  "R");
+
+        error = NULL;
+        if (!gdm_smartcard_manager_start (manager, &error)) {
+            g_object_unref (manager);
+            manager = NULL;
+
+            if (error != NULL) {
+                    g_debug ("%s", error->message);
+                    g_error_free (error);
+            } else {
+                    g_debug ("could not start smartcard manager");
+
+            }
+            goto out;
+        }
+out:
+        g_key_file_free (cfg);
+}
+
+static void
+stop_watching_for_smartcards (void)
+{
+        if (manager != NULL) {
+                gdm_smartcard_manager_stop (manager);
+                g_object_unref (manager);
+                manager = NULL;
+        }
+}
+
+static void
+on_alrm_signal (int signal_number)
+{
+        raise (SIGKILL);
+}
+
+static void
+on_term_signal (int signal_number)
+{
+        close (signal_pipe_fds[1]);
+        signal_pipe_fds[1] = -1;
+
+        /* Give us 10 seconds to clean up orderly.
+         * If that fails, then the smartcard stack
+         * is hung up and we need to die hard
+         */
+        alarm (10);
+        signal (SIGALRM, on_alrm_signal);
+}
+
+static gboolean
+after_term_signal (GIOChannel   *io_channel,
+                   GIOCondition  condition,
+                   gpointer      data)
+{
+        g_main_loop_quit (event_loop);
+        return FALSE;
+}
+
+static void
+on_debug_message (const char     *log_domain,
+                  GLogLevelFlags  log_level,
+                  const char     *message,
+                  gpointer        user_data)
+{
+        g_printerr ("*** DEBUG: %s\n", message);
+}
+
+int
+main (int    argc,
+      char **argv)
+{
+        GIOChannel *io_channel;
+
+        setlocale (LC_ALL, "");
+
+        g_log_set_handler (NULL, G_LOG_LEVEL_DEBUG, on_debug_message, NULL);
+
+        event_loop = g_main_loop_new (NULL, FALSE);
+
+        watch_for_smartcards ();
+
+        if (pipe (signal_pipe_fds) != 0) {
+                return 1;
+        }
+        fcntl (signal_pipe_fds[0], F_SETFD, FD_CLOEXEC);
+        fcntl (signal_pipe_fds[1], F_SETFD, FD_CLOEXEC);
+
+        io_channel = g_io_channel_unix_new (signal_pipe_fds[0]);
+        g_io_channel_set_flags (io_channel, G_IO_FLAG_NONBLOCK, NULL);
+        g_io_channel_set_encoding (io_channel, NULL, NULL);
+        g_io_channel_set_buffered (io_channel, FALSE);
+        g_io_add_watch (io_channel, G_IO_HUP, after_term_signal, NULL);
+        g_io_channel_set_close_on_unref (io_channel, TRUE);
+        g_io_channel_unref (io_channel);
+
+        signal (SIGTERM, on_term_signal);
+        signal (SIGPIPE, on_term_signal);
+
+#ifdef HAVE_SYS_PRCTL_H
+        prctl (PR_SET_PDEATHSIG, SIGKILL);
+#endif
+
+        g_main_loop_run (event_loop);
+
+        stop_watching_for_smartcards ();
+
+        return 0;
+}
diff --git a/gui/simple-greeter/extensions/smartcard/gdm-smartcard.c 
b/gui/simple-greeter/extensions/smartcard/gdm-smartcard.c
new file mode 100644
index 0000000..7792146
--- /dev/null
+++ b/gui/simple-greeter/extensions/smartcard/gdm-smartcard.c
@@ -0,0 +1,552 @@
+/* gdm-smartcard.c - smartcard object
+ *
+ * Copyright (C) 2006 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+#define GDM_SMARTCARD_ENABLE_INTERNAL_API
+#include "gdm-smartcard.h"
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <cert.h>
+#include <nss.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <secmod.h>
+#include <secerr.h>
+
+struct _GdmSmartcardPrivate {
+        SECMODModule *module;
+        GdmSmartcardState state;
+
+        CK_SLOT_ID slot_id;
+        int slot_series;
+
+        PK11SlotInfo *slot;
+        char *name;
+
+        CERTCertificate *signing_certificate;
+        CERTCertificate *encryption_certificate;
+};
+
+static void gdm_smartcard_finalize (GObject *object);
+static void gdm_smartcard_class_install_signals (GdmSmartcardClass *card_class);
+static void gdm_smartcard_class_install_properties (GdmSmartcardClass *card_class);
+static void gdm_smartcard_set_property (GObject       *object,
+                                       guint          prop_id,
+                                       const GValue  *value,
+                                       GParamSpec    *pspec);
+static void gdm_smartcard_get_property (GObject     *object,
+                                       guint        prop_id,
+                                       GValue      *value,
+                                       GParamSpec  *pspec);
+static void gdm_smartcard_set_name (GdmSmartcard *card, const char *name);
+static void gdm_smartcard_set_slot_id (GdmSmartcard *card,
+                                      int                 slot_id);
+static void gdm_smartcard_set_slot_series (GdmSmartcard *card,
+                                          int          slot_series);
+static void gdm_smartcard_set_module (GdmSmartcard *card,
+                                     SECMODModule *module);
+
+static PK11SlotInfo *gdm_smartcard_find_slot_from_id (GdmSmartcard *card,
+                                                     int slot_id);
+
+static PK11SlotInfo *gdm_smartcard_find_slot_from_card_name (GdmSmartcard *card,
+                                                            const char  *card_name);
+
+#ifndef GDM_SMARTCARD_DEFAULT_SLOT_ID
+#define GDM_SMARTCARD_DEFAULT_SLOT_ID ((gulong) -1)
+#endif
+
+#ifndef GDM_SMARTCARD_DEFAULT_SLOT_SERIES
+#define GDM_SMARTCARD_DEFAULT_SLOT_SERIES -1
+#endif
+
+enum {
+        PROP_0 = 0,
+        PROP_NAME,
+        PROP_SLOT_ID,
+        PROP_SLOT_SERIES,
+        PROP_MODULE,
+        NUMBER_OF_PROPERTIES
+};
+
+enum {
+        INSERTED,
+        REMOVED,
+        NUMBER_OF_SIGNALS
+};
+
+static guint gdm_smartcard_signals[NUMBER_OF_SIGNALS];
+
+G_DEFINE_TYPE (GdmSmartcard, gdm_smartcard, G_TYPE_OBJECT);
+
+static void
+gdm_smartcard_class_init (GdmSmartcardClass *card_class)
+{
+        GObjectClass *gobject_class;
+
+        gobject_class = G_OBJECT_CLASS (card_class);
+
+        gobject_class->finalize = gdm_smartcard_finalize;
+
+        gdm_smartcard_class_install_signals (card_class);
+        gdm_smartcard_class_install_properties (card_class);
+
+        g_type_class_add_private (card_class,
+                                  sizeof (GdmSmartcardPrivate));
+}
+
+static void
+gdm_smartcard_class_install_signals (GdmSmartcardClass *card_class)
+{
+        GObjectClass *object_class;
+
+        object_class = G_OBJECT_CLASS (card_class);
+
+        gdm_smartcard_signals[INSERTED] =
+                g_signal_new ("inserted",
+                          G_OBJECT_CLASS_TYPE (object_class),
+                          G_SIGNAL_RUN_LAST,
+                          G_STRUCT_OFFSET (GdmSmartcardClass,
+                                           inserted),
+                          NULL, NULL, g_cclosure_marshal_VOID__VOID,
+                          G_TYPE_NONE, 0);
+
+        gdm_smartcard_signals[REMOVED] =
+                g_signal_new ("removed",
+                          G_OBJECT_CLASS_TYPE (object_class),
+                          G_SIGNAL_RUN_LAST,
+                          G_STRUCT_OFFSET (GdmSmartcardClass,
+                                           removed),
+                          NULL, NULL, g_cclosure_marshal_VOID__VOID,
+                          G_TYPE_NONE, 0);
+}
+
+static void
+gdm_smartcard_class_install_properties (GdmSmartcardClass *card_class)
+{
+        GObjectClass *object_class;
+        GParamSpec *param_spec;
+
+        object_class = G_OBJECT_CLASS (card_class);
+        object_class->set_property = gdm_smartcard_set_property;
+        object_class->get_property = gdm_smartcard_get_property;
+
+        param_spec = g_param_spec_ulong ("slot-id", _("Slot ID"),
+                                   _("The slot the card is in"),
+                                   1, G_MAXULONG,
+                                   GDM_SMARTCARD_DEFAULT_SLOT_ID,
+                                   G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+        g_object_class_install_property (object_class, PROP_SLOT_ID, param_spec);
+
+        param_spec = g_param_spec_int ("slot-series", _("Slot Series"),
+                                   _("per-slot card identifier"),
+                                   -1, G_MAXINT,
+                                   GDM_SMARTCARD_DEFAULT_SLOT_SERIES,
+                                   G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+        g_object_class_install_property (object_class, PROP_SLOT_SERIES, param_spec);
+
+        param_spec = g_param_spec_string ("name", _("name"),
+                                      _("name"), NULL,
+                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+        g_object_class_install_property (object_class, PROP_NAME, param_spec);
+
+        param_spec = g_param_spec_pointer ("module", _("Module"),
+                                       _("smartcard driver"),
+                                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
+        g_object_class_install_property (object_class, PROP_MODULE, param_spec);
+}
+
+static void
+gdm_smartcard_set_property (GObject       *object,
+                            guint          prop_id,
+                            const GValue  *value,
+                            GParamSpec    *pspec)
+{
+        GdmSmartcard *card = GDM_SMARTCARD (object);
+
+        switch (prop_id) {
+                case PROP_NAME:
+                        gdm_smartcard_set_name (card, g_value_get_string (value));
+                        break;
+
+                case PROP_SLOT_ID:
+                        gdm_smartcard_set_slot_id (card,
+                                                   g_value_get_ulong (value));
+                        break;
+
+                case PROP_SLOT_SERIES:
+                        gdm_smartcard_set_slot_series (card,
+                                                       g_value_get_int (value));
+                        break;
+
+                case PROP_MODULE:
+                        gdm_smartcard_set_module (card,
+                                                  (SECMODModule *)
+                                                  g_value_get_pointer (value));
+                        break;
+
+                default:
+                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        }
+}
+
+CK_SLOT_ID
+gdm_smartcard_get_slot_id (GdmSmartcard *card)
+{
+        return card->priv->slot_id;
+}
+
+GdmSmartcardState
+gdm_smartcard_get_state (GdmSmartcard *card)
+{
+        return card->priv->state;
+}
+
+char *
+gdm_smartcard_get_name (GdmSmartcard *card)
+{
+        return g_strdup (card->priv->name);
+}
+
+gboolean
+gdm_smartcard_is_login_card (GdmSmartcard *card)
+{
+        const char *login_card_name;
+        login_card_name = g_getenv ("PKCS11_LOGIN_TOKEN_NAME");
+
+        if ((login_card_name == NULL) || (card->priv->name == NULL)) {
+                return FALSE;
+        }
+
+        if (strcmp (card->priv->name, login_card_name) == 0) {
+                return TRUE;
+        }
+
+        return FALSE;
+}
+
+static void
+gdm_smartcard_get_property (GObject    *object,
+                            guint        prop_id,
+                            GValue      *value,
+                            GParamSpec  *pspec)
+{
+        GdmSmartcard *card = GDM_SMARTCARD (object);
+
+        switch (prop_id) {
+                case PROP_NAME:
+                        g_value_take_string (value,
+                                             gdm_smartcard_get_name (card));
+                        break;
+
+                case PROP_SLOT_ID:
+                        g_value_set_ulong (value,
+                                           (gulong) gdm_smartcard_get_slot_id (card));
+                        break;
+
+                case PROP_SLOT_SERIES:
+                        g_value_set_int (value,
+                                         gdm_smartcard_get_slot_series (card));
+                        break;
+
+                default:
+                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        }
+}
+
+static void
+gdm_smartcard_set_name (GdmSmartcard *card,
+                        const char   *name)
+{
+        if (name == NULL) {
+                return;
+        }
+
+        if ((card->priv->name == NULL) ||
+            (strcmp (card->priv->name, name) != 0)) {
+                g_free (card->priv->name);
+                card->priv->name = g_strdup (name);
+
+                if (card->priv->slot == NULL) {
+                        card->priv->slot = gdm_smartcard_find_slot_from_card_name (card,
+                                                                                     card->priv->name);
+
+                        if (card->priv->slot != NULL) {
+                                int slot_id, slot_series;
+
+                                slot_id = PK11_GetSlotID (card->priv->slot);
+                                if (slot_id != card->priv->slot_id) {
+                                        gdm_smartcard_set_slot_id (card, slot_id);
+                                }
+
+                                slot_series = PK11_GetSlotSeries (card->priv->slot);
+                                if (slot_series != card->priv->slot_series) {
+                                        gdm_smartcard_set_slot_series (card, slot_series);
+                                }
+
+                                _gdm_smartcard_set_state (card, GDM_SMARTCARD_STATE_INSERTED);
+                        } else {
+                                _gdm_smartcard_set_state (card, GDM_SMARTCARD_STATE_REMOVED);
+                        }
+                }
+
+                g_object_notify (G_OBJECT (card), "name");
+        }
+}
+
+static void
+gdm_smartcard_set_slot_id (GdmSmartcard *card,
+                           int           slot_id)
+{
+        if (card->priv->slot_id != slot_id) {
+                card->priv->slot_id = slot_id;
+
+                if (card->priv->slot == NULL) {
+                        card->priv->slot = gdm_smartcard_find_slot_from_id (card,
+                                                                             card->priv->slot_id);
+
+                        if (card->priv->slot != NULL) {
+                                const char *card_name;
+
+                                card_name = PK11_GetTokenName (card->priv->slot);
+                                if ((card->priv->name == NULL) ||
+                                    ((card_name != NULL) &&
+                                    (strcmp (card_name, card->priv->name) != 0))) {
+                                        gdm_smartcard_set_name (card, card_name);
+                                }
+
+                                _gdm_smartcard_set_state (card, GDM_SMARTCARD_STATE_INSERTED);
+                        } else {
+                                _gdm_smartcard_set_state (card, GDM_SMARTCARD_STATE_REMOVED);
+                        }
+                }
+
+                g_object_notify (G_OBJECT (card), "slot-id");
+        }
+}
+
+static void
+gdm_smartcard_set_slot_series (GdmSmartcard *card,
+                               int           slot_series)
+{
+        if (card->priv->slot_series != slot_series) {
+                card->priv->slot_series = slot_series;
+                g_object_notify (G_OBJECT (card), "slot-series");
+        }
+}
+
+static void
+gdm_smartcard_set_module (GdmSmartcard *card,
+                          SECMODModule *module)
+{
+        gboolean should_notify;
+
+        if (card->priv->module != module) {
+                should_notify = TRUE;
+        } else {
+                should_notify = FALSE;
+        }
+
+        if (card->priv->module != NULL) {
+                SECMOD_DestroyModule (card->priv->module);
+                card->priv->module = NULL;
+        }
+
+        if (module != NULL) {
+                card->priv->module = SECMOD_ReferenceModule (module);
+        }
+
+        if (should_notify) {
+                g_object_notify (G_OBJECT (card), "module");
+        }
+}
+
+int
+gdm_smartcard_get_slot_series (GdmSmartcard *card)
+{
+        return card->priv->slot_series;
+}
+
+static void
+gdm_smartcard_init (GdmSmartcard *card)
+{
+
+        g_debug ("initializing smartcard ");
+
+        card->priv = G_TYPE_INSTANCE_GET_PRIVATE (card,
+                                                  GDM_TYPE_SMARTCARD,
+                                                  GdmSmartcardPrivate);
+}
+
+static void gdm_smartcard_finalize (GObject *object)
+{
+        GdmSmartcard *card;
+        GObjectClass *gobject_class;
+
+        card = GDM_SMARTCARD (object);
+
+        g_free (card->priv->name);
+
+        gdm_smartcard_set_module (card, NULL);
+
+        gobject_class = G_OBJECT_CLASS (gdm_smartcard_parent_class);
+
+        gobject_class->finalize (object);
+}
+
+GQuark gdm_smartcard_error_quark (void)
+{
+        static GQuark error_quark = 0;
+
+        if (error_quark == 0) {
+                error_quark = g_quark_from_static_string ("gdm-smartcard-error-quark");
+        }
+
+        return error_quark;
+}
+
+GdmSmartcard *
+_gdm_smartcard_new (SECMODModule *module,
+                    CK_SLOT_ID    slot_id,
+                    int           slot_series)
+{
+        GdmSmartcard *card;
+
+        g_return_val_if_fail (module != NULL, NULL);
+        g_return_val_if_fail (slot_id >= 1, NULL);
+        g_return_val_if_fail (slot_series > 0, NULL);
+        g_return_val_if_fail (sizeof (gulong) == sizeof (slot_id), NULL);
+
+        card = GDM_SMARTCARD (g_object_new (GDM_TYPE_SMARTCARD,
+                                             "module", module,
+                                             "slot-id", (gulong) slot_id,
+                                             "slot-series", slot_series,
+                                             NULL));
+        return card;
+}
+
+GdmSmartcard *
+_gdm_smartcard_new_from_name (SECMODModule *module,
+                              const char   *name)
+{
+        GdmSmartcard *card;
+
+        g_return_val_if_fail (module != NULL, NULL);
+        g_return_val_if_fail (name != NULL, NULL);
+
+        card = GDM_SMARTCARD (g_object_new (GDM_TYPE_SMARTCARD,
+                                            "module", module,
+                                            "name", name,
+                                            NULL));
+        return card;
+}
+
+void
+_gdm_smartcard_set_state (GdmSmartcard      *card,
+                          GdmSmartcardState  state)
+{
+        if (card->priv->state != state) {
+                card->priv->state = state;
+
+                if (state == GDM_SMARTCARD_STATE_INSERTED) {
+                        g_signal_emit (card, gdm_smartcard_signals[INSERTED], 0);
+                } else if (state == GDM_SMARTCARD_STATE_REMOVED) {
+                        g_signal_emit (card, gdm_smartcard_signals[REMOVED], 0);
+                } else {
+                        g_assert_not_reached ();
+                }
+        }
+}
+
+/* So we could conceivably make the closure data a pointer to the card
+ * or something similiar and then emit signals when we want passwords,
+ * but it's probably easier to just get the password up front and use
+ * it.  So we just take the passed in g_malloc'd (well probably, who knows)
+ * and strdup it using NSPR's memory allocation routines.
+ */
+static char *
+gdm_smartcard_password_handler (PK11SlotInfo *slot,
+                                PRBool        is_retrying,
+                                const char   *password)
+{
+        if (is_retrying) {
+                return NULL;
+        }
+
+        return password != NULL? PL_strdup (password): NULL;
+}
+
+gboolean
+gdm_smartcard_unlock (GdmSmartcard *card,
+                      const char   *password)
+{
+        SECStatus status;
+
+        PK11_SetPasswordFunc ((PK11PasswordFunc) gdm_smartcard_password_handler);
+
+        /* we pass PR_TRUE to load certificates
+        */
+        status = PK11_Authenticate (card->priv->slot, PR_TRUE, (gpointer) password);
+
+        if (status != SECSuccess) {
+                g_debug ("could not unlock card - %d", status);
+                return FALSE;
+        }
+        return TRUE;
+}
+
+static PK11SlotInfo *
+gdm_smartcard_find_slot_from_card_name (GdmSmartcard *card,
+                                        const char   *card_name)
+{
+        int i;
+
+        for (i = 0; i < card->priv->module->slotCount; i++) {
+                const char *slot_card_name;
+
+                slot_card_name = PK11_GetTokenName (card->priv->module->slots[i]);
+
+                if ((slot_card_name != NULL) &&
+                    (strcmp (slot_card_name, card_name) == 0)) {
+                        return card->priv->module->slots[i];
+                }
+        }
+
+        return NULL;
+}
+
+static PK11SlotInfo *
+gdm_smartcard_find_slot_from_id (GdmSmartcard *card,
+                                 int           slot_id)
+{
+        int i;
+
+        for (i = 0; i < card->priv->module->slotCount; i++) {
+                if (PK11_GetSlotID (card->priv->module->slots[i]) == slot_id) {
+                        return card->priv->module->slots[i];
+                }
+        }
+
+        return NULL;
+}
diff --git a/gui/simple-greeter/extensions/smartcard/gdm-smartcard.h 
b/gui/simple-greeter/extensions/smartcard/gdm-smartcard.h
new file mode 100644
index 0000000..9f153cd
--- /dev/null
+++ b/gui/simple-greeter/extensions/smartcard/gdm-smartcard.h
@@ -0,0 +1,94 @@
+/* securitycard.h - api for reading and writing data to a security card
+ *
+ * Copyright (C) 2006 Ray Strode
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+#ifndef GDM_SMARTCARD_H
+#define GDM_SMARTCARD_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <secmod.h>
+
+G_BEGIN_DECLS
+#define GDM_TYPE_SMARTCARD            (gdm_smartcard_get_type ())
+#define GDM_SMARTCARD(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_SMARTCARD, GdmSmartcard))
+#define GDM_SMARTCARD_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_SMARTCARD, 
GdmSmartcardClass))
+#define GDM_IS_SMARTCARD(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDM_TYPE_SMARTCARD))
+#define GDM_IS_SMARTCARD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDM_TYPE_SMARTCARD))
+#define GDM_SMARTCARD_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GDM_TYPE_SMARTCARD, 
GdmSmartcardClass))
+#define GDM_SMARTCARD_ERROR           (gdm_smartcard_error_quark ())
+typedef struct _GdmSmartcardClass GdmSmartcardClass;
+typedef struct _GdmSmartcard GdmSmartcard;
+typedef struct _GdmSmartcardPrivate GdmSmartcardPrivate;
+typedef enum _GdmSmartcardError GdmSmartcardError;
+typedef enum _GdmSmartcardState GdmSmartcardState;
+
+typedef struct _GdmSmartcardRequest GdmSmartcardRequest;
+
+struct _GdmSmartcard {
+    GObject parent;
+
+    /*< private > */
+    GdmSmartcardPrivate *priv;
+};
+
+struct _GdmSmartcardClass {
+    GObjectClass parent_class;
+
+    void (* inserted) (GdmSmartcard *card);
+    void (* removed)  (GdmSmartcard *card);
+};
+
+enum _GdmSmartcardError {
+    GDM_SMARTCARD_ERROR_GENERIC = 0,
+};
+
+enum _GdmSmartcardState {
+    GDM_SMARTCARD_STATE_INSERTED = 0,
+    GDM_SMARTCARD_STATE_REMOVED,
+};
+
+GType gdm_smartcard_get_type (void) G_GNUC_CONST;
+GQuark gdm_smartcard_error_quark (void) G_GNUC_CONST;
+
+CK_SLOT_ID gdm_smartcard_get_slot_id (GdmSmartcard *card);
+gint gdm_smartcard_get_slot_series (GdmSmartcard *card);
+GdmSmartcardState gdm_smartcard_get_state (GdmSmartcard *card);
+
+char *gdm_smartcard_get_name (GdmSmartcard *card);
+gboolean gdm_smartcard_is_login_card (GdmSmartcard *card);
+
+gboolean gdm_smartcard_unlock (GdmSmartcard *card,
+                               const char   *password);
+
+/* don't under any circumstances call these functions */
+#ifdef GDM_SMARTCARD_ENABLE_INTERNAL_API
+
+GdmSmartcard *_gdm_smartcard_new (SECMODModule *module,
+                                  CK_SLOT_ID    slot_id,
+                                  gint          slot_series);
+GdmSmartcard *_gdm_smartcard_new_from_name (SECMODModule *module,
+                                            const char   *name);
+
+void _gdm_smartcard_set_state (GdmSmartcard      *card,
+                               GdmSmartcardState  state);
+#endif
+
+G_END_DECLS
+#endif                                /* GDM_SMARTCARD_H */
diff --git a/gui/simple-greeter/extensions/smartcard/icons/16x16/Makefile.am 
b/gui/simple-greeter/extensions/smartcard/icons/16x16/Makefile.am
new file mode 100644
index 0000000..661d687
--- /dev/null
+++ b/gui/simple-greeter/extensions/smartcard/icons/16x16/Makefile.am
@@ -0,0 +1,5 @@
+iconsdir = $(datadir)/icons/hicolor/16x16/apps
+
+icons_DATA = gdm-smartcard.png
+
+EXTRA_DIST = $(icons_DATA)
diff --git a/gui/simple-greeter/extensions/smartcard/icons/16x16/gdm-smartcard.png 
b/gui/simple-greeter/extensions/smartcard/icons/16x16/gdm-smartcard.png
new file mode 100644
index 0000000..0112af1
Binary files /dev/null and b/gui/simple-greeter/extensions/smartcard/icons/16x16/gdm-smartcard.png differ
diff --git a/gui/simple-greeter/extensions/smartcard/icons/48x48/Makefile.am 
b/gui/simple-greeter/extensions/smartcard/icons/48x48/Makefile.am
new file mode 100644
index 0000000..e79d85b
--- /dev/null
+++ b/gui/simple-greeter/extensions/smartcard/icons/48x48/Makefile.am
@@ -0,0 +1,5 @@
+iconsdir = $(datadir)/icons/hicolor/48x48/apps
+
+icons_DATA = gdm-smartcard.png
+
+EXTRA_DIST = $(icons_DATA)
diff --git a/gui/simple-greeter/extensions/smartcard/icons/48x48/gdm-smartcard.png 
b/gui/simple-greeter/extensions/smartcard/icons/48x48/gdm-smartcard.png
new file mode 100644
index 0000000..35d5578
Binary files /dev/null and b/gui/simple-greeter/extensions/smartcard/icons/48x48/gdm-smartcard.png differ
diff --git a/gui/simple-greeter/extensions/smartcard/icons/Makefile.am 
b/gui/simple-greeter/extensions/smartcard/icons/Makefile.am
new file mode 100644
index 0000000..c20f10d
--- /dev/null
+++ b/gui/simple-greeter/extensions/smartcard/icons/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = 16x16 48x48
diff --git a/gui/simple-greeter/extensions/smartcard/page.ui b/gui/simple-greeter/extensions/smartcard/page.ui
new file mode 100644
index 0000000..8fa5c7b
--- /dev/null
+++ b/gui/simple-greeter/extensions/smartcard/page.ui
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.14"/>
+    <object class="GtkVBox" id="page">
+      <property name="visible">True</property>
+      <property name="orientation">vertical</property>
+      <child>
+        <object class="GtkHBox" id="auth-input-box">
+          <property name="visible">True</property>
+          <property name="spacing">6</property>
+          <child>
+            <object class="GtkLabel" id="auth-prompt-label">
+              <property name="visible">True</property>
+            </object>
+            <packing>
+              <property name="expand">False</property>
+              <property name="fill">False</property>
+              <property name="position">0</property>
+            </packing>
+          </child>
+          <child>
+            <object class="GtkEntry" id="auth-prompt-entry">
+              <property name="visible">True</property>
+              <property name="can_focus">True</property>
+              <property name="activates_default">True</property>
+            </object>
+            <packing>
+              <property name="position">1</property>
+            </packing>
+          </child>
+        </object>
+        <packing>
+          <property name="expand">True</property>
+          <property name="fill">True</property>
+          <property name="position">0</property>
+        </packing>
+      </child>
+      <child>
+        <object class="GtkHBox" id="auth-message-box">
+          <property name="visible">True</property>
+          <child>
+            <object class="GtkLabel" id="auth-message-label">
+              <property name="visible">True</property>
+            </object>
+            <packing>
+              <property name="position">0</property>
+            </packing>
+          </child>
+        </object>
+        <packing>
+          <property name="expand">True</property>
+          <property name="fill">True</property>
+          <property name="position">1</property>
+        </packing>
+      </child>
+    </object>
+</interface>
diff --git a/gui/simple-greeter/extensions/unified/Makefile.am 
b/gui/simple-greeter/extensions/unified/Makefile.am
new file mode 100644
index 0000000..31fa744
--- /dev/null
+++ b/gui/simple-greeter/extensions/unified/Makefile.am
@@ -0,0 +1,42 @@
+NULL =
+PAM_SERVICE_NAME = gdm
+
+extensiondir = $(GDM_SIMPLE_GREETER_EXTENSIONS_DATA_DIR)/unified
+extension_DATA = page.ui
+
+AM_CPPFLAGS = \
+       -I$(top_srcdir)/common                          \
+       -I$(top_srcdir)/gui/simple-greeter/libgdmsimplegreeter  \
+       -DDMCONFDIR=\""$(dmconfdir)"\"                  \
+       -DGDMCONFDIR=\"$(gdmconfdir)\"                  \
+       -DPLUGINDATADIR=\""$(extensiondir)"\"           \
+       -DGDM_UNIFIED_EXTENSION_SERVICE_NAME=\""$(PAM_SERVICE_NAME)"\"  \
+       -DSYSCONFDIR=\""$(sysconfdir)"\"                \
+       -DLIBLOCALEDIR=\""$(prefix)/lib/locale"\"       \
+       -DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
+       -DLIBEXECDIR=\""$(libexecdir)"\"                \
+       -DSBINDIR=\""$(sbindir)"\"                      \
+       $(DISABLE_DEPRECATED_CFLAGS)    \
+       $(GTK_CFLAGS)                                   \
+       $(SIMPLE_GREETER_CFLAGS)                        \
+       $(POLKIT_GNOME_CFLAGS)                          \
+       $(NULL)
+
+noinst_LTLIBRARIES = libunified.la
+
+libunified_la_CFLAGS =                 \
+       $(SIMPLE_GREETER_CFLAGS)        \
+       $(NULL)
+
+libunified_la_LDFLAGS = -export-dynamic
+libunified_la_LIBADD = ../../../../common/libgdmcommon.la \
+               ../../libgdmsimplegreeter/libgdmsimplegreeter.la
+libunified_la_SOURCES =                                \
+                       gdm-unified-extension.h \
+                       gdm-unified-extension.c
+
+EXTRA_DIST = $(extension_DATA)
+
+MAINTAINERCLEANFILES =                  \
+        *~                              \
+        Makefile.in
diff --git a/gui/simple-greeter/extensions/unified/gdm-unified-extension.c 
b/gui/simple-greeter/extensions/unified/gdm-unified-extension.c
new file mode 100644
index 0000000..a9f89b8
--- /dev/null
+++ b/gui/simple-greeter/extensions/unified/gdm-unified-extension.c
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Written By: Ray Strode <rstrode redhat com>
+ *
+ */
+
+#include <config.h>
+#include "gdm-unified-extension.h"
+#include "gdm-login-extension.h"
+
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+struct _GdmUnifiedExtensionPrivate
+{
+        GIcon     *icon;
+        GtkWidget *page;
+        GtkActionGroup *actions;
+        GtkAction *login_action;
+
+        GtkWidget *message_label;
+        GtkWidget *prompt_label;
+        GtkWidget *prompt_entry;
+
+        GQueue    *message_queue;
+        guint      message_timeout_id;
+
+        guint      answer_pending : 1;
+};
+
+typedef struct {
+        char                       *text;
+        GdmServiceMessageType  type;
+} QueuedMessage;
+
+static void gdm_unified_extension_finalize (GObject *object);
+
+static void gdm_login_extension_iface_init (GdmLoginExtensionIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GdmUnifiedExtension,
+                         gdm_unified_extension,
+                         G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDM_TYPE_LOGIN_EXTENSION,
+                                                gdm_login_extension_iface_init));
+
+static void
+set_message (GdmUnifiedExtension *extension,
+             const char           *message)
+{
+        gtk_widget_show (extension->priv->message_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->message_label), message);
+}
+
+static void
+free_queued_message (QueuedMessage *message)
+{
+        g_free (message->text);
+        g_slice_free (QueuedMessage, message);
+}
+
+static void
+purge_message_queue (GdmUnifiedExtension *extension)
+{
+        if (extension->priv->message_timeout_id) {
+                g_source_remove (extension->priv->message_timeout_id);
+                extension->priv->message_timeout_id = 0;
+        }
+        g_queue_foreach (extension->priv->message_queue,
+                         (GFunc) free_queued_message,
+                         NULL);
+        g_queue_clear (extension->priv->message_queue);
+}
+
+static gboolean
+dequeue_message (GdmUnifiedExtension *extension)
+{
+        if (!g_queue_is_empty (extension->priv->message_queue)) {
+                int duration;
+                gboolean needs_beep;
+
+                QueuedMessage *message;
+                message = (QueuedMessage *) g_queue_pop_head (extension->priv->message_queue);
+
+                switch (message->type) {
+                        case GDM_SERVICE_MESSAGE_TYPE_INFO:
+                                needs_beep = FALSE;
+                                break;
+                        case GDM_SERVICE_MESSAGE_TYPE_PROBLEM:
+                                needs_beep = TRUE;
+                                break;
+                        default:
+                                g_assert_not_reached ();
+                }
+
+                set_message (extension, message->text);
+
+                duration = (int) (g_utf8_strlen (message->text, -1) / 66.0) * 1000;
+                duration = CLAMP (duration, 400, 3000);
+
+                extension->priv->message_timeout_id = g_timeout_add (duration,
+                                                                     (GSourceFunc) dequeue_message,
+                                                                     extension);
+                if (needs_beep) {
+                        gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (extension->priv->page)));
+                }
+
+                free_queued_message (message);
+        } else {
+                extension->priv->message_timeout_id = 0;
+
+                _gdm_login_extension_emit_message_queue_empty (GDM_LOGIN_EXTENSION (extension));
+        }
+
+        return FALSE;
+}
+
+static void
+gdm_unified_extension_queue_message (GdmLoginExtension   *login_extension,
+                                      GdmServiceMessageType  type,
+                                      const char            *text)
+{
+        GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension);
+
+        QueuedMessage *message = g_slice_new (QueuedMessage);
+
+        message->text = g_strdup (text);
+        message->type = type;
+
+        g_queue_push_tail (extension->priv->message_queue, message);
+
+        if (extension->priv->message_timeout_id == 0) {
+                dequeue_message (extension);
+        }
+}
+
+static void
+gdm_unified_extension_ask_question (GdmLoginExtension *login_extension,
+                                     const char          *message)
+{
+        GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension);
+        gtk_widget_show (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE);
+        gtk_widget_show (extension->priv->prompt_entry);
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        extension->priv->answer_pending = TRUE;
+
+        gtk_action_set_sensitive (extension->priv->login_action, TRUE);
+}
+
+static void
+gdm_unified_extension_ask_secret (GdmLoginExtension *login_extension,
+                                   const char          *message)
+{
+        GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension);
+        gtk_widget_show (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message);
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), FALSE);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_widget_show (extension->priv->prompt_entry);
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        extension->priv->answer_pending = TRUE;
+
+        gtk_action_set_sensitive (extension->priv->login_action, TRUE);
+}
+
+static void
+gdm_unified_extension_reset (GdmLoginExtension *login_extension)
+{
+        GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension);
+        gtk_widget_hide (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), "");
+
+        gtk_widget_hide (extension->priv->prompt_entry);
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+        gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE);
+        extension->priv->answer_pending = FALSE;
+
+        set_message (extension, "");
+        purge_message_queue (extension);
+
+        gdm_login_extension_set_enabled (login_extension, FALSE);
+}
+
+static void
+gdm_unified_extension_set_ready (GdmLoginExtension *extension)
+{
+        gdm_login_extension_set_enabled (extension, TRUE);
+}
+
+static char *
+gdm_unified_extension_get_service_name (GdmLoginExtension *extension)
+{
+        return g_strdup (GDM_UNIFIED_EXTENSION_SERVICE_NAME);
+}
+
+static GtkWidget *
+gdm_unified_extension_get_page (GdmLoginExtension *login_extension)
+{
+        GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension);
+        return extension->priv->page;
+}
+
+static GtkActionGroup *
+gdm_unified_extension_get_actions (GdmLoginExtension *login_extension)
+{
+        GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension);
+        return g_object_ref (extension->priv->actions);
+}
+
+static void
+request_answer (GdmUnifiedExtension *extension)
+{
+        const char *text;
+
+        if (!extension->priv->answer_pending) {
+                _gdm_login_extension_emit_answer (GDM_LOGIN_EXTENSION (extension), NULL);
+                return;
+        }
+
+        extension->priv->answer_pending = FALSE;
+        text = gtk_entry_get_text (GTK_ENTRY (extension->priv->prompt_entry));
+        _gdm_login_extension_emit_answer (GDM_LOGIN_EXTENSION (extension), text);
+
+        gtk_widget_hide (extension->priv->prompt_entry);
+        gtk_widget_hide (extension->priv->prompt_label);
+        gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), "");
+        gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), "");
+}
+
+static gboolean
+gdm_unified_extension_focus (GdmLoginExtension *login_extension)
+{
+        GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension);
+        if (!extension->priv->answer_pending) {
+                _gdm_login_extension_emit_answer (login_extension, NULL);
+                return FALSE;
+        }
+
+        gtk_widget_grab_focus (extension->priv->prompt_entry);
+        return TRUE;
+}
+
+static gboolean
+gdm_unified_extension_has_queued_messages (GdmLoginExtension *login_extension)
+{
+        GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension);
+
+        if (extension->priv->message_timeout_id != 0) {
+                return TRUE;
+        }
+
+        if (!g_queue_is_empty (extension->priv->message_queue)) {
+                return TRUE;
+        }
+
+        return FALSE;
+}
+
+static GIcon *
+gdm_unified_extension_get_icon (GdmLoginExtension *login_extension)
+{
+        GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension);
+        return g_object_ref (extension->priv->icon);
+}
+
+static char *
+gdm_unified_extension_get_name (GdmLoginExtension *login_extension)
+{
+        return g_strdup (_("Authentication"));
+}
+
+static char *
+gdm_unified_extension_get_description (GdmLoginExtension *login_extension)
+{
+        return g_strdup (_("Log into session"));
+}
+
+static gboolean
+gdm_unified_extension_is_choosable (GdmLoginExtension *login_extension)
+{
+        return FALSE;
+}
+
+static gboolean
+gdm_unified_extension_is_visible (GdmLoginExtension *login_extension)
+{
+        return TRUE;
+}
+
+static void
+gdm_login_extension_iface_init (GdmLoginExtensionIface *iface)
+{
+        iface->get_icon = gdm_unified_extension_get_icon;
+        iface->get_description = gdm_unified_extension_get_description;
+        iface->get_name = gdm_unified_extension_get_name;
+        iface->is_choosable = gdm_unified_extension_is_choosable;
+        iface->is_visible = gdm_unified_extension_is_visible;
+        iface->queue_message = gdm_unified_extension_queue_message;
+        iface->ask_question = gdm_unified_extension_ask_question;
+        iface->ask_secret = gdm_unified_extension_ask_secret;
+        iface->reset = gdm_unified_extension_reset;
+        iface->set_ready = gdm_unified_extension_set_ready;
+        iface->get_service_name = gdm_unified_extension_get_service_name;
+        iface->get_page = gdm_unified_extension_get_page;
+        iface->get_actions = gdm_unified_extension_get_actions;
+        iface->focus = gdm_unified_extension_focus;
+        iface->has_queued_messages = gdm_unified_extension_has_queued_messages;
+}
+
+static void
+gdm_unified_extension_class_init (GdmUnifiedExtensionClass *extension_class)
+{
+        GObjectClass *object_class;
+
+        object_class = G_OBJECT_CLASS (extension_class);
+
+        object_class->finalize = gdm_unified_extension_finalize;
+
+        g_type_class_add_private (extension_class,
+                                  sizeof (GdmUnifiedExtensionPrivate));
+}
+
+static void
+gdm_unified_extension_finalize (GObject *object)
+{
+        GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (object);
+
+        purge_message_queue (extension);
+}
+
+static void
+on_activate_log_in (GdmUnifiedExtension *extension,
+                    GtkAction            *action)
+{
+        request_answer (extension);
+        gtk_action_set_sensitive (action, FALSE);
+}
+
+static void
+create_page (GdmUnifiedExtension *extension)
+{
+        GtkBuilder *builder;
+        GObject *object;
+        GError *error;
+
+        builder = gtk_builder_new ();
+
+        error = NULL;
+        gtk_builder_add_from_file (builder,
+                                   PLUGINDATADIR "/page.ui",
+                                   &error);
+
+        if (error != NULL) {
+                g_warning ("Could not load UI file: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        object = gtk_builder_get_object (builder, "page");
+        g_object_ref (object);
+
+        extension->priv->page = GTK_WIDGET (object);
+
+        object = gtk_builder_get_object (builder, "auth-prompt-label");
+        g_object_ref (object);
+        extension->priv->prompt_label = GTK_WIDGET (object);
+        gtk_widget_hide (extension->priv->prompt_label);
+
+        object = gtk_builder_get_object (builder, "auth-prompt-entry");
+        g_object_ref (object);
+        extension->priv->prompt_entry = GTK_WIDGET (object);
+        gtk_widget_hide (extension->priv->prompt_entry);
+
+        object = gtk_builder_get_object (builder, "auth-message-label");
+        g_object_ref (object);
+        extension->priv->message_label = GTK_WIDGET (object);
+        gtk_widget_show (extension->priv->message_label);
+
+        g_object_unref (builder);
+}
+
+static void
+create_actions (GdmUnifiedExtension *extension)
+{
+        GtkAction *action;
+
+        extension->priv->actions = gtk_action_group_new (GDM_UNIFIED_EXTENSION_NAME);
+
+        action = gtk_action_new (GDM_LOGIN_EXTENSION_DEFAULT_ACTION,
+                                 _("Log In"), NULL, NULL);
+        g_signal_connect_swapped (action, "activate",
+                                  G_CALLBACK (on_activate_log_in), extension);
+        g_object_set (G_OBJECT (action), "icon-name", "go-home", NULL);
+        gtk_action_group_add_action (extension->priv->actions,
+                                     action);
+
+        extension->priv->login_action = action;
+}
+
+static void
+gdm_unified_extension_init (GdmUnifiedExtension *extension)
+{
+        extension->priv = G_TYPE_INSTANCE_GET_PRIVATE (extension,
+                                                       GDM_TYPE_UNIFIED_EXTENSION,
+                                                       GdmUnifiedExtensionPrivate);
+
+        extension->priv->icon = g_themed_icon_new ("dialog-unified");
+        create_page (extension);
+        create_actions (extension);
+
+        extension->priv->message_queue = g_queue_new ();
+
+        gdm_unified_extension_reset (GDM_LOGIN_EXTENSION (extension));
+}
+
+void
+gdm_unified_extension_load (void)
+{
+        g_io_extension_point_implement (GDM_LOGIN_EXTENSION_POINT_NAME,
+                                        GDM_TYPE_UNIFIED_EXTENSION,
+                                        GDM_UNIFIED_EXTENSION_NAME,
+                                        G_MAXINT);
+}
diff --git a/gui/simple-greeter/extensions/unified/gdm-unified-extension.h 
b/gui/simple-greeter/extensions/unified/gdm-unified-extension.h
new file mode 100644
index 0000000..d6b0eaa
--- /dev/null
+++ b/gui/simple-greeter/extensions/unified/gdm-unified-extension.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * Written By: Ray Strode <rstrode redhat com>
+ */
+
+#ifndef __GDM_UNIFIED_EXTENSION_H
+#define __GDM_UNIFIED_EXTENSION_H
+
+#include <glib-object.h>
+#include "gdm-login-extension.h"
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_UNIFIED_EXTENSION (gdm_unified_extension_get_type ())
+#define GDM_UNIFIED_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_UNIFIED_EXTENSION, 
GdmUnifiedExtension))
+#define GDM_UNIFIED_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_UNIFIED_EXTENSION, 
GdmUnifiedExtensionClass))
+#define GDM_IS_UNIFIED_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDM_TYPE_UNIFIED_EXTENSION))
+#define GDM_IS_UNIFIED_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDM_TYPE_UNIFIED_EXTENSION))
+#define GDM_UNIFIED_EXTENSION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GDM_TYPE_UNIFIED_EXTENSION, 
GdmUnifiedExtensionClass))
+
+#define GDM_UNIFIED_EXTENSION_NAME "gdm-unified-extension"
+
+typedef struct _GdmUnifiedExtensionPrivate GdmUnifiedExtensionPrivate;
+
+typedef struct
+{
+        GObject                  parent;
+        GdmUnifiedExtensionPrivate *priv;
+} GdmUnifiedExtension;
+
+typedef struct
+{
+        GObjectClass parent_class;
+} GdmUnifiedExtensionClass;
+
+GType                 gdm_unified_extension_get_type      (void);
+void                  gdm_unified_extension_load          (void);
+
+G_END_DECLS
+
+#endif /* GDM_UNIFIED_EXTENSION_H */
diff --git a/gui/simple-greeter/extensions/unified/gdm.pam b/gui/simple-greeter/extensions/unified/gdm.pam
new file mode 100644
index 0000000..58c397d
--- /dev/null
+++ b/gui/simple-greeter/extensions/unified/gdm.pam
@@ -0,0 +1,12 @@
+#%PAM-1.0
+auth       required    pam_env.so
+auth       required    pam_succeed_if.so user != root quiet
+auth       sufficient  pam_succeed_if.so user ingroup nopasswdlogin
+auth       include     system-auth
+account    required    pam_nologin.so
+account    include     system-auth
+password   include     system-auth
+session    optional    pam_keyinit.so force revoke
+session    include     system-auth
+session    required    pam_loginuid.so
+session    optional    pam_console.so
diff --git a/gui/simple-greeter/extensions/unified/page.ui b/gui/simple-greeter/extensions/unified/page.ui
new file mode 100644
index 0000000..8fa5c7b
--- /dev/null
+++ b/gui/simple-greeter/extensions/unified/page.ui
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.14"/>
+    <object class="GtkVBox" id="page">
+      <property name="visible">True</property>
+      <property name="orientation">vertical</property>
+      <child>
+        <object class="GtkHBox" id="auth-input-box">
+          <property name="visible">True</property>
+          <property name="spacing">6</property>
+          <child>
+            <object class="GtkLabel" id="auth-prompt-label">
+              <property name="visible">True</property>
+            </object>
+            <packing>
+              <property name="expand">False</property>
+              <property name="fill">False</property>
+              <property name="position">0</property>
+            </packing>
+          </child>
+          <child>
+            <object class="GtkEntry" id="auth-prompt-entry">
+              <property name="visible">True</property>
+              <property name="can_focus">True</property>
+              <property name="activates_default">True</property>
+            </object>
+            <packing>
+              <property name="position">1</property>
+            </packing>
+          </child>
+        </object>
+        <packing>
+          <property name="expand">True</property>
+          <property name="fill">True</property>
+          <property name="position">0</property>
+        </packing>
+      </child>
+      <child>
+        <object class="GtkHBox" id="auth-message-box">
+          <property name="visible">True</property>
+          <child>
+            <object class="GtkLabel" id="auth-message-label">
+              <property name="visible">True</property>
+            </object>
+            <packing>
+              <property name="position">0</property>
+            </packing>
+          </child>
+        </object>
+        <packing>
+          <property name="expand">True</property>
+          <property name="fill">True</property>
+          <property name="position">1</property>
+        </packing>
+      </child>
+    </object>
+</interface>
diff --git a/gui/simple-greeter/gdm-cell-renderer-timer.c b/gui/simple-greeter/gdm-cell-renderer-timer.c
new file mode 100644
index 0000000..708b0e6
--- /dev/null
+++ b/gui/simple-greeter/gdm-cell-renderer-timer.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *  Written by: Ray Strode <rstrode redhat com>
+ */
+
+#include "config.h"
+#include "gdm-cell-renderer-timer.h"
+#include <glib/gi18n.h>
+
+#define GDM_CELL_RENDERER_TIMER_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), 
GDM_TYPE_CELL_RENDERER_TIMER, GdmCellRendererTimerPrivate))
+
+struct _GdmCellRendererTimerPrivate
+{
+        gdouble    value;
+};
+
+enum
+{
+        PROP_0,
+        PROP_VALUE,
+};
+
+G_DEFINE_TYPE (GdmCellRendererTimer, gdm_cell_renderer_timer, GTK_TYPE_CELL_RENDERER)
+
+static void
+gdm_cell_renderer_timer_get_property (GObject *object,
+                                      guint param_id,
+                                      GValue *value,
+                                      GParamSpec *pspec)
+{
+        GdmCellRendererTimer *renderer;
+
+        renderer = GDM_CELL_RENDERER_TIMER (object);
+
+        switch (param_id) {
+                case PROP_VALUE:
+                        g_value_set_double (value, renderer->priv->value);
+                        break;
+                default:
+                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+        }
+}
+
+static void
+gdm_cell_renderer_timer_set_value (GdmCellRendererTimer *renderer,
+                                   gdouble               value)
+{
+        renderer->priv->value = value;
+}
+
+static void
+gdm_cell_renderer_timer_set_property (GObject *object,
+                                      guint param_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+        GdmCellRendererTimer *renderer;
+
+        renderer = GDM_CELL_RENDERER_TIMER (object);
+
+        switch (param_id) {
+                case PROP_VALUE:
+                        gdm_cell_renderer_timer_set_value (renderer,
+                                                           g_value_get_double (value));
+                        break;
+                default:
+                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+    }
+}
+
+static void
+gdm_cell_renderer_timer_get_size (GtkCellRenderer    *cell,
+                                  GtkWidget          *widget,
+                                  const GdkRectangle *cell_area,
+                                  gint               *x_offset,
+                                  gint               *y_offset,
+                                  gint               *width,
+                                  gint               *height)
+{
+
+        GdmCellRendererTimer *renderer;
+
+        renderer = GDM_CELL_RENDERER_TIMER (cell);
+
+        if (cell_area != NULL) {
+                if (x_offset != NULL) {
+                        *x_offset = 0;
+                }
+
+                if (y_offset != NULL) {
+                        *y_offset = 0;
+                }
+        }
+
+       gfloat xpad, ypad;
+       gtk_cell_renderer_get_alignment (cell, &xpad, &ypad);
+
+        if (width != NULL) {
+                *width = xpad * 2 + 24;
+        }
+
+        if (height != NULL) {
+                *height = ypad * 2 + 24;
+        }
+}
+
+static double
+get_opacity_for_value (double value)
+{
+        const double start_value = 0.05;
+        const double end_value = 0.33;
+
+        if (value < start_value) {
+                return 0.0;
+        }
+
+        if (value >= end_value) {
+                return 1.0;
+        }
+
+        return ((value - start_value) / (end_value - start_value));
+}
+
+static void
+draw_timer (GdmCellRendererTimer *renderer,
+            cairo_t              *context,
+            GdkColor             *fg,
+            GdkColor             *bg,
+            int                   width,
+            int                   height)
+{
+        double radius;
+        double opacity;
+
+        opacity = get_opacity_for_value (renderer->priv->value);
+
+        if (opacity <= G_MINDOUBLE) {
+                return;
+        }
+
+        radius = .5 * (MIN (width, height) / 2.0);
+
+        cairo_translate (context, width / 2., height / 2.);
+
+        cairo_set_source_rgba (context,
+                               fg->red / 65535.0,
+                               fg->green / 65535.0,
+                               fg->blue / 65535.0,
+                               opacity);
+
+        cairo_move_to (context, 0, 0);
+        cairo_arc (context, 0, 0, radius + 1, 0, 2 * G_PI);
+        cairo_fill (context);
+
+        cairo_set_source_rgb (context,
+                              bg->red / 65535.0,
+                              bg->green / 65535.0,
+                              bg->blue / 65535.0);
+        cairo_move_to (context, 0, 0);
+        cairo_arc (context, 0, 0, radius, - G_PI / 2,
+                   renderer->priv->value * 2 * G_PI - G_PI / 2);
+        cairo_clip (context);
+        cairo_paint_with_alpha (context, opacity);
+}
+
+static void
+gdm_cell_renderer_timer_render (GtkCellRenderer      *cell,
+                                cairo_t              *context,
+                                GtkWidget            *widget,
+                                const GdkRectangle   *background_area,
+                                const GdkRectangle   *cell_area,
+                                GtkCellRendererState  renderer_state)
+{
+        GdmCellRendererTimer *renderer;
+        GtkStateType          widget_state;
+        gfloat                xpad, ypad;
+
+        renderer = GDM_CELL_RENDERER_TIMER (cell);
+
+        if (renderer->priv->value <= G_MINDOUBLE) {
+                return;
+        }
+
+        gtk_cell_renderer_get_alignment (cell, &xpad, &ypad);
+
+        cairo_translate (context,
+                         cell_area->x + xpad,
+                         cell_area->y + ypad);
+
+        widget_state = GTK_STATE_NORMAL;
+        if (renderer_state & GTK_CELL_RENDERER_SELECTED) {
+                if (gtk_widget_has_focus (widget)) {
+                        widget_state = GTK_STATE_SELECTED;
+                } else {
+                        widget_state = GTK_STATE_ACTIVE;
+                }
+        }
+
+        if (renderer_state & GTK_CELL_RENDERER_INSENSITIVE) {
+                widget_state = GTK_STATE_INSENSITIVE;
+        }
+
+        draw_timer (renderer, context,
+                    &gtk_widget_get_style (widget)->text_aa[widget_state],
+                    &gtk_widget_get_style (widget)->base[widget_state],
+                    cell_area->width, cell_area->height);
+}
+
+static void
+gdm_cell_renderer_timer_class_init (GdmCellRendererTimerClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+        GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);
+
+        object_class->get_property = gdm_cell_renderer_timer_get_property;
+        object_class->set_property = gdm_cell_renderer_timer_set_property;
+
+        cell_class->get_size = gdm_cell_renderer_timer_get_size;
+        cell_class->render = gdm_cell_renderer_timer_render;
+
+        g_object_class_install_property (object_class,
+                                         PROP_VALUE,
+                                         g_param_spec_double ("value",
+                                         _("Value"),
+                                         _("percentage of time complete"),
+                                         0.0, 1.0, 0.0,
+                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+        g_type_class_add_private (object_class,
+                            sizeof (GdmCellRendererTimerPrivate));
+}
+
+static void
+gdm_cell_renderer_timer_init (GdmCellRendererTimer *renderer)
+{
+        renderer->priv = GDM_CELL_RENDERER_TIMER_GET_PRIVATE (renderer);
+}
+
+GtkCellRenderer*
+gdm_cell_renderer_timer_new (void)
+{
+        return g_object_new (GDM_TYPE_CELL_RENDERER_TIMER, NULL);
+}
+
diff --git a/gui/simple-greeter/gdm-cell-renderer-timer.h b/gui/simple-greeter/gdm-cell-renderer-timer.h
new file mode 100644
index 0000000..a3661d0
--- /dev/null
+++ b/gui/simple-greeter/gdm-cell-renderer-timer.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *  Written by: Ray Strode <rstrode redhat com>
+ */
+#ifndef __GDM_CELL_RENDERER_TIMER_H
+#define __GDM_CELL_RENDERER_TIMER_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_CELL_RENDERER_TIMER (gdm_cell_renderer_timer_get_type ())
+#define GDM_CELL_RENDERER_TIMER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_CELL_RENDERER_TIMER, 
GdmCellRendererTimer))
+#define GDM_CELL_RENDERER_TIMER_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), 
GDM_TYPE_CELL_RENDERER_TIMER, GdmCellRendererTimerClass))
+#define GTK_IS_CELL_RENDERER_TIMER(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GDM_TYPE_CELL_RENDERER_TIMER))
+#define GTK_IS_CELL_RENDERER_TIMER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GDM_TYPE_CELL_RENDERER_TIMER))
+#define GDM_CELL_RENDERER_TIMER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GDM_TYPE_CELL_RENDERER_TIMER, GdmCellRendererTimerClass))
+
+typedef struct _GdmCellRendererTimer         GdmCellRendererTimer;
+typedef struct _GdmCellRendererTimerClass    GdmCellRendererTimerClass;
+typedef struct _GdmCellRendererTimerPrivate  GdmCellRendererTimerPrivate;
+
+struct _GdmCellRendererTimer
+{
+  GtkCellRenderer              parent;
+
+  /*< private >*/
+  GdmCellRendererTimerPrivate *priv;
+};
+
+struct _GdmCellRendererTimerClass
+{
+  GtkCellRendererClass parent_class;
+};
+
+GType           gdm_cell_renderer_timer_get_type (void);
+GtkCellRenderer* gdm_cell_renderer_timer_new      (void);
+
+G_END_DECLS
+
+#endif  /* __GDM_CELL_RENDERER_TIMER_H */
diff --git a/gui/simple-greeter/gdm-chooser-widget.c b/gui/simple-greeter/gdm-chooser-widget.c
new file mode 100644
index 0000000..cab506c
--- /dev/null
+++ b/gui/simple-greeter/gdm-chooser-widget.c
@@ -0,0 +1,2849 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 Ray Strode <rstrode redhat com>
+ * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 <syslog.h>
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include "gdm-chooser-widget.h"
+#include "gdm-scrollable-widget.h"
+#include "gdm-cell-renderer-timer.h"
+#include "gdm-timer.h"
+
+#define GDM_CHOOSER_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_CHOOSER_WIDGET, 
GdmChooserWidgetPrivate))
+
+#ifndef GDM_CHOOSER_WIDGET_DEFAULT_ICON_SIZE
+#define GDM_CHOOSER_WIDGET_DEFAULT_ICON_SIZE 64
+#endif
+
+typedef enum {
+        GDM_CHOOSER_WIDGET_STATE_GROWN = 0,
+        GDM_CHOOSER_WIDGET_STATE_GROWING,
+        GDM_CHOOSER_WIDGET_STATE_SHRINKING,
+        GDM_CHOOSER_WIDGET_STATE_SHRUNK,
+} GdmChooserWidgetState;
+
+struct GdmChooserWidgetPrivate
+{
+        GtkWidget                *frame;
+        GtkWidget                *frame_alignment;
+        GtkWidget                *scrollable_widget;
+
+        GtkWidget                *items_view;
+        GtkListStore             *list_store;
+
+        GtkTreeModelFilter       *model_filter;
+        GtkTreeModelSort         *model_sorter;
+
+        GdkPixbuf                *is_in_use_pixbuf;
+
+        /* row for the list_store model */
+        GtkTreeRowReference      *active_row;
+        GtkTreeRowReference      *separator_row;
+
+        GHashTable               *rows_with_timers;
+
+        GtkTreeViewColumn        *status_column;
+        GtkTreeViewColumn        *image_column;
+
+        char                     *inactive_text;
+        char                     *active_text;
+        char                     *in_use_message;
+
+        gint                     number_of_normal_rows;
+        gint                     number_of_separated_rows;
+        gint                     number_of_rows_with_status;
+        gint                     number_of_rows_with_images;
+        gint                     number_of_active_timers;
+
+        guint                    update_idle_id;
+        guint                    update_separator_idle_id;
+        guint                    update_cursor_idle_id;
+        guint                    update_visibility_idle_id;
+        guint                    update_items_idle_id;
+        guint                    timer_animation_timeout_id;
+
+        gboolean                 list_visible;
+
+        guint32                  should_hide_inactive_items : 1;
+        guint32                  emit_activated_after_resize_animation : 1;
+
+        GdmChooserWidgetPosition separator_position;
+        GdmChooserWidgetState    state;
+
+        double                   active_row_normalized_position;
+};
+
+enum {
+        PROP_0,
+        PROP_INACTIVE_TEXT,
+        PROP_ACTIVE_TEXT,
+        PROP_LIST_VISIBLE
+};
+
+enum {
+        ACTIVATED = 0,
+        DEACTIVATED,
+        LOADED,
+        NUMBER_OF_SIGNALS
+};
+
+static guint    signals[NUMBER_OF_SIGNALS];
+
+static void     gdm_chooser_widget_class_init  (GdmChooserWidgetClass *klass);
+static void     gdm_chooser_widget_init        (GdmChooserWidget      *chooser_widget);
+static void     gdm_chooser_widget_finalize    (GObject               *object);
+
+static void     update_timer_from_time         (GdmChooserWidget    *widget,
+                                                GtkTreeRowReference *row,
+                                                double               now);
+
+G_DEFINE_TYPE (GdmChooserWidget, gdm_chooser_widget, GTK_TYPE_ALIGNMENT)
+
+enum {
+        CHOOSER_IMAGE_COLUMN = 0,
+        CHOOSER_NAME_COLUMN,
+        CHOOSER_COMMENT_COLUMN,
+        CHOOSER_PRIORITY_COLUMN,
+        CHOOSER_ITEM_IS_IN_USE_COLUMN,
+        CHOOSER_ITEM_IS_SEPARATED_COLUMN,
+        CHOOSER_ITEM_IS_VISIBLE_COLUMN,
+        CHOOSER_TIMER_START_TIME_COLUMN,
+        CHOOSER_TIMER_DURATION_COLUMN,
+        CHOOSER_TIMER_VALUE_COLUMN,
+        CHOOSER_ID_COLUMN,
+        CHOOSER_LOAD_FUNC_COLUMN,
+        CHOOSER_LOAD_DATA_COLUMN,
+        NUMBER_OF_CHOOSER_COLUMNS
+};
+
+static gboolean
+find_item (GdmChooserWidget *widget,
+           const char       *id,
+           GtkTreeIter      *iter)
+{
+        GtkTreeModel *model;
+        gboolean      found_item;
+
+        g_assert (GDM_IS_CHOOSER_WIDGET (widget));
+        g_assert (id != NULL);
+
+        found_item = FALSE;
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        if (!gtk_tree_model_get_iter_first (model, iter)) {
+                return FALSE;
+        }
+
+        do {
+                char *item_id;
+
+                gtk_tree_model_get (model,
+                                    iter,
+                                    CHOOSER_ID_COLUMN,
+                                    &item_id,
+                                    -1);
+
+                g_assert (item_id != NULL);
+
+                if (strcmp (id, item_id) == 0) {
+                        found_item = TRUE;
+                }
+                g_free (item_id);
+
+        } while (!found_item && gtk_tree_model_iter_next (model, iter));
+
+        return found_item;
+}
+
+typedef struct {
+        GdmChooserWidget           *widget;
+        GdmChooserUpdateForeachFunc func;
+        gpointer                    user_data;
+} UpdateForeachData;
+
+static gboolean
+foreach_item (GtkTreeModel      *model,
+              GtkTreePath       *path,
+              GtkTreeIter       *iter,
+              UpdateForeachData *data)
+{
+        GdkPixbuf *image;
+        char      *name;
+        char      *comment;
+        gboolean   in_use;
+        gboolean   is_separate;
+        gboolean   res;
+        char      *id;
+        gulong     priority;
+
+        gtk_tree_model_get (model,
+                            iter,
+                            CHOOSER_ID_COLUMN, &id,
+                            CHOOSER_IMAGE_COLUMN, &image,
+                            CHOOSER_NAME_COLUMN, &name,
+                            CHOOSER_COMMENT_COLUMN, &comment,
+                            CHOOSER_PRIORITY_COLUMN, &priority,
+                            CHOOSER_ITEM_IS_IN_USE_COLUMN, &in_use,
+                            CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate,
+                            -1);
+        res = data->func (data->widget,
+                          (const char *)id,
+                          &image,
+                          &name,
+                          &comment,
+                          &priority,
+                          &in_use,
+                          &is_separate,
+                          data->user_data);
+        if (res) {
+                gtk_list_store_set (GTK_LIST_STORE (model),
+                                    iter,
+                                    CHOOSER_ID_COLUMN, id,
+                                    CHOOSER_IMAGE_COLUMN, image,
+                                    CHOOSER_NAME_COLUMN, name,
+                                    CHOOSER_COMMENT_COLUMN, comment,
+                                    CHOOSER_PRIORITY_COLUMN, priority,
+                                    CHOOSER_ITEM_IS_IN_USE_COLUMN, in_use,
+                                    CHOOSER_ITEM_IS_SEPARATED_COLUMN, is_separate,
+                                    -1);
+        }
+
+        g_free (name);
+        g_free (comment);
+        if (image != NULL) {
+                g_object_unref (image);
+        }
+
+        return FALSE;
+}
+
+void
+gdm_chooser_widget_update_foreach_item (GdmChooserWidget           *widget,
+                                        GdmChooserUpdateForeachFunc func,
+                                        gpointer                    user_data)
+{
+        UpdateForeachData fdata;
+
+        fdata.widget = widget;
+        fdata.func = func;
+        fdata.user_data = user_data;
+        gtk_tree_model_foreach (GTK_TREE_MODEL (widget->priv->list_store),
+                                (GtkTreeModelForeachFunc) foreach_item,
+                                &fdata);
+}
+
+static void
+translate_list_path_to_view_path (GdmChooserWidget  *widget,
+                                  GtkTreePath      **path)
+{
+        GtkTreePath *filtered_path;
+        GtkTreePath *sorted_path;
+
+        /* the child model is the source for the filter */
+        filtered_path = gtk_tree_model_filter_convert_child_path_to_path (widget->priv->model_filter,
+                                                                          *path);
+        sorted_path = gtk_tree_model_sort_convert_child_path_to_path (widget->priv->model_sorter,
+                                                                      filtered_path);
+        gtk_tree_path_free (filtered_path);
+
+        gtk_tree_path_free (*path);
+        *path = sorted_path;
+}
+
+
+static void
+translate_view_path_to_list_path (GdmChooserWidget  *widget,
+                                  GtkTreePath      **path)
+{
+        GtkTreePath *filtered_path;
+        GtkTreePath *list_path;
+
+        /* the child model is the source for the filter */
+        filtered_path = gtk_tree_model_sort_convert_path_to_child_path (widget->priv->model_sorter,
+                                                                        *path);
+
+        list_path = gtk_tree_model_filter_convert_path_to_child_path (widget->priv->model_filter,
+                                                                      filtered_path);
+        gtk_tree_path_free (filtered_path);
+
+        gtk_tree_path_free (*path);
+        *path = list_path;
+}
+
+static GtkTreePath *
+get_list_path_to_active_row (GdmChooserWidget *widget)
+{
+        GtkTreePath *path;
+
+        if (widget->priv->active_row == NULL) {
+                return NULL;
+        }
+
+        path = gtk_tree_row_reference_get_path (widget->priv->active_row);
+        if (path == NULL) {
+                return NULL;
+        }
+
+        return path;
+}
+
+static GtkTreePath *
+get_view_path_to_active_row (GdmChooserWidget *widget)
+{
+        GtkTreePath *path;
+
+        path = get_list_path_to_active_row (widget);
+        if (path == NULL) {
+                return NULL;
+        }
+
+        translate_list_path_to_view_path (widget, &path);
+
+        return path;
+}
+
+static char *
+get_active_item_id (GdmChooserWidget *widget,
+                    GtkTreeIter      *iter)
+{
+        char         *item_id;
+        GtkTreeModel *model;
+        GtkTreePath  *path;
+
+        g_return_val_if_fail (GDM_IS_CHOOSER_WIDGET (widget), NULL);
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+        item_id = NULL;
+
+        if (widget->priv->active_row == NULL) {
+                return NULL;
+        }
+
+        path = get_list_path_to_active_row (widget);
+        if (path == NULL) {
+                return NULL;
+        }
+
+        if (gtk_tree_model_get_iter (model, iter, path)) {
+                gtk_tree_model_get (model,
+                                    iter,
+                                    CHOOSER_ID_COLUMN,
+                                    &item_id,
+                                    -1);
+        }
+        gtk_tree_path_free (path);
+
+        return item_id;
+}
+
+char *
+gdm_chooser_widget_get_active_item (GdmChooserWidget *widget)
+{
+        GtkTreeIter iter;
+
+        return get_active_item_id (widget, &iter);
+}
+
+static void
+get_selected_list_path (GdmChooserWidget *widget,
+                        GtkTreePath     **pathp)
+{
+        GtkTreeSelection    *selection;
+        GtkTreeModel        *sort_model;
+        GtkTreeIter          sorted_iter;
+        GtkTreePath         *path;
+
+        path = NULL;
+
+        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view));
+        if (gtk_tree_selection_get_selected (selection, &sort_model, &sorted_iter)) {
+
+                g_assert (sort_model == GTK_TREE_MODEL (widget->priv->model_sorter));
+
+                path = gtk_tree_model_get_path (sort_model, &sorted_iter);
+
+                translate_view_path_to_list_path (widget, &path);
+        } else {
+                g_debug ("GdmChooserWidget: no rows selected");
+        }
+
+        *pathp = path;
+}
+
+char *
+gdm_chooser_widget_get_selected_item (GdmChooserWidget *widget)
+{
+        GtkTreeIter   iter;
+        GtkTreeModel *model;
+        GtkTreePath  *path;
+        char         *id;
+
+        id = NULL;
+
+        get_selected_list_path (widget, &path);
+
+        if (path == NULL) {
+                return NULL;
+        }
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        if (gtk_tree_model_get_iter (model, &iter, path)) {
+                gtk_tree_model_get (model,
+                                    &iter,
+                                    CHOOSER_ID_COLUMN,
+                                    &id,
+                                    -1);
+        }
+
+        gtk_tree_path_free (path);
+
+        return id;
+}
+
+void
+gdm_chooser_widget_set_selected_item (GdmChooserWidget *widget,
+                                      const char       *id)
+{
+        GtkTreeIter       iter;
+        GtkTreeSelection *selection;
+        GtkTreeModel     *model;
+
+        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
+
+        g_debug ("GdmChooserWidget: setting selected item '%s'",
+                 id ? id : "(null)");
+
+        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view));
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        if (find_item (widget, id, &iter)) {
+                GtkTreePath  *path;
+
+                path = gtk_tree_model_get_path (model, &iter);
+                translate_list_path_to_view_path (widget, &path);
+
+                gtk_tree_selection_select_path (selection, path);
+
+                gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget->priv->items_view),
+                                              path,
+                                              NULL,
+                                              TRUE,
+                                              0.5,
+                                              0.0);
+
+                gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view),
+                                          path,
+                                          NULL,
+                                          FALSE);
+                gtk_tree_path_free (path);
+        } else {
+                gtk_tree_selection_unselect_all (selection);
+        }
+}
+
+static void
+activate_from_item_id (GdmChooserWidget *widget,
+                       const char       *item_id)
+{
+        GtkTreeModel *model;
+        GtkTreePath  *path;
+        GtkTreeIter   iter;
+        char         *path_str;
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+        path = NULL;
+
+        if (find_item (widget, item_id, &iter)) {
+                path = gtk_tree_model_get_path (model, &iter);
+
+                path_str = gtk_tree_path_to_string (path);
+                g_debug ("GdmChooserWidget: got list path '%s'", path_str);
+                g_free (path_str);
+
+                translate_list_path_to_view_path (widget, &path);
+
+                path_str = gtk_tree_path_to_string (path);
+                g_debug ("GdmChooserWidget: translated to view path '%s'", path_str);
+                g_free (path_str);
+        }
+
+        if (path == NULL) {
+                g_debug ("GdmChooserWidget: unable to activate - path for item '%s' not found", item_id);
+                return;
+        }
+
+        gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget->priv->items_view),
+                                      path,
+                                      NULL,
+                                      TRUE,
+                                      0.5,
+                                      0.0);
+
+        gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view),
+                                  path,
+                                  NULL,
+                                  FALSE);
+
+        gtk_tree_view_row_activated (GTK_TREE_VIEW (widget->priv->items_view),
+                                     path,
+                                     NULL);
+        gtk_tree_path_free (path);
+}
+
+static void
+set_frame_text (GdmChooserWidget *widget,
+                const char       *text)
+{
+        GtkWidget *label;
+
+        label = gtk_frame_get_label_widget (GTK_FRAME (widget->priv->frame));
+
+        if (text == NULL && label != NULL) {
+                gtk_frame_set_label_widget (GTK_FRAME (widget->priv->frame),
+                                            NULL);
+                gtk_alignment_set_padding (GTK_ALIGNMENT (widget->priv->frame_alignment),
+                                           0, 0, 0, 0);
+        } else if (text != NULL && label == NULL) {
+                label = gtk_label_new ("");
+                gtk_label_set_mnemonic_widget (GTK_LABEL (label),
+                                               widget->priv->items_view);
+                gtk_widget_show (label);
+                gtk_frame_set_label_widget (GTK_FRAME (widget->priv->frame),
+                                            label);
+                gtk_alignment_set_padding (GTK_ALIGNMENT (widget->priv->frame_alignment),
+                                           0, 0, 0, 0);
+        }
+
+        if (label != NULL && text != NULL) {
+                char *markup;
+                markup = g_strdup_printf ("%s", text);
+                gtk_label_set_markup_with_mnemonic (GTK_LABEL (label), markup);
+                g_free (markup);
+        }
+}
+
+static void
+on_shrink_animation_step (GdmScrollableWidget *scrollable_widget,
+                          double               progress,
+                          int                 *new_height,
+                          GdmChooserWidget    *widget)
+{
+        GtkTreePath   *active_row_path;
+        const double   final_alignment = 0.5;
+        double         row_alignment;
+
+        active_row_path = get_view_path_to_active_row (widget);
+        row_alignment = widget->priv->active_row_normalized_position + progress * (final_alignment - 
widget->priv->active_row_normalized_position);
+
+        gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget->priv->items_view),
+                                     active_row_path, NULL, TRUE, row_alignment, 0.0);
+        gtk_tree_path_free (active_row_path);
+}
+
+static gboolean
+update_separator_visibility (GdmChooserWidget *widget)
+{
+        GtkTreePath *separator_path;
+        GtkTreeIter  iter;
+        gboolean     is_visible;
+
+        g_debug ("GdmChooserWidget: updating separator visibility");
+
+        separator_path = gtk_tree_row_reference_get_path (widget->priv->separator_row);
+
+        if (separator_path == NULL) {
+                goto out;
+        }
+
+        gtk_tree_model_get_iter (GTK_TREE_MODEL (widget->priv->list_store),
+                                 &iter, separator_path);
+
+        if (widget->priv->number_of_normal_rows > 0 &&
+            widget->priv->number_of_separated_rows > 0 &&
+            widget->priv->state != GDM_CHOOSER_WIDGET_STATE_SHRUNK) {
+                is_visible = TRUE;
+        } else {
+                is_visible = FALSE;
+        }
+
+        gtk_list_store_set (widget->priv->list_store,
+                            &iter,
+                            CHOOSER_ITEM_IS_VISIBLE_COLUMN, is_visible,
+                            -1);
+
+ out:
+        widget->priv->update_separator_idle_id = 0;
+        return FALSE;
+}
+
+static void
+queue_update_separator_visibility (GdmChooserWidget *widget)
+{
+        if (widget->priv->update_separator_idle_id == 0) {
+                g_debug ("GdmChooserWidget: queuing update separator visibility");
+
+                widget->priv->update_separator_idle_id =
+                        g_idle_add ((GSourceFunc) update_separator_visibility, widget);
+        }
+}
+
+static gboolean
+update_visible_items (GdmChooserWidget *widget)
+{
+        GtkTreePath *path;
+        GtkTreePath *end;
+        GtkTreeIter  iter;
+
+        if (! gtk_tree_view_get_visible_range (GTK_TREE_VIEW (widget->priv->items_view), &path, &end)) {
+                g_debug ("GdmChooserWidget: Unable to get visible range");
+                goto out;
+        }
+
+        for (; gtk_tree_path_compare (path, end) <= 0; gtk_tree_path_next (path)) {
+                char                        *id;
+                gpointer                     user_data;
+                GdmChooserWidgetItemLoadFunc func;
+
+                if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (widget->priv->model_sorter), &iter, path))
+                        break;
+
+                id = NULL;
+                gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->model_sorter),
+                                    &iter,
+                                    CHOOSER_ID_COLUMN, &id,
+                                    CHOOSER_LOAD_FUNC_COLUMN, &func,
+                                    CHOOSER_LOAD_DATA_COLUMN, &user_data,
+                                    -1);
+                if (id != NULL && func != NULL) {
+                        GtkTreeIter  child_iter;
+                        GtkTreeIter  list_iter;
+
+                        g_debug ("Updating for %s", id);
+
+                        gtk_tree_model_sort_convert_iter_to_child_iter (widget->priv->model_sorter,
+                                                                        &child_iter,
+                                                                        &iter);
+
+                        gtk_tree_model_filter_convert_iter_to_child_iter (widget->priv->model_filter,
+                                                                          &list_iter,
+                                                                          &child_iter);
+                        /* remove the func so it doesn't need to load again */
+                        gtk_list_store_set (GTK_LIST_STORE (widget->priv->list_store),
+                                            &list_iter,
+                                            CHOOSER_LOAD_FUNC_COLUMN, NULL,
+                                            -1);
+
+                        func (widget, id, user_data);
+                }
+
+                g_free (id);
+        }
+
+        gtk_tree_path_free (path);
+        gtk_tree_path_free (end);
+ out:
+        widget->priv->update_items_idle_id = 0;
+
+        return FALSE;
+}
+
+static void
+set_chooser_list_visible (GdmChooserWidget *widget,
+                          gboolean          is_visible)
+{
+        if (widget->priv->list_visible != is_visible) {
+                widget->priv->list_visible = is_visible;
+                g_object_notify (G_OBJECT (widget), "list-visible");
+        }
+}
+
+static gboolean
+update_chooser_visibility (GdmChooserWidget *widget)
+{
+        update_visible_items (widget);
+
+        if (gdm_chooser_widget_get_number_of_items (widget) > 0) {
+                gtk_widget_show (widget->priv->frame);
+                set_chooser_list_visible (widget, gtk_widget_get_visible (GTK_WIDGET (widget)));
+        } else {
+                gtk_widget_hide (widget->priv->frame);
+                set_chooser_list_visible (widget, FALSE);
+        }
+
+        widget->priv->update_visibility_idle_id = 0;
+
+        return FALSE;
+}
+
+static inline gboolean
+iters_equal (GtkTreeIter *a,
+             GtkTreeIter *b)
+{
+        if (a->stamp != b->stamp)
+                return FALSE;
+
+        if (a->user_data != b->user_data)
+                return FALSE;
+
+        /* user_data2 and user_data3 are not used in GtkListStore */
+
+        return TRUE;
+}
+
+static void
+set_inactive_items_visible (GdmChooserWidget *widget,
+                            gboolean          should_show)
+{
+        GtkTreeModel *model;
+        GtkTreeModel *view_model;
+        char         *active_item_id;
+        GtkTreeIter   active_item_iter;
+        GtkTreeIter   iter;
+
+        g_debug ("setting inactive items visible");
+
+        active_item_id = get_active_item_id (widget, &active_item_iter);
+        if (active_item_id == NULL) {
+                g_debug ("GdmChooserWidget: No active item set");
+        }
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        if (!gtk_tree_model_get_iter_first (model, &iter)) {
+                goto out;
+        }
+
+        /* unset tree view model to hide row add/remove signals from gail */
+        view_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->items_view));
+        g_object_ref (view_model);
+        gtk_tree_view_set_model (GTK_TREE_VIEW (widget->priv->items_view), NULL);
+
+        g_debug ("GdmChooserWidget: Setting inactive items visible: %s", should_show ? "true" : "false");
+
+        do {
+                if (active_item_id == NULL || !iters_equal (&active_item_iter, &iter)) {
+                        /* inactive item */
+                        gtk_list_store_set (widget->priv->list_store,
+                                            &iter,
+                                            CHOOSER_ITEM_IS_VISIBLE_COLUMN, should_show,
+                                            -1);
+                } else {
+                        /* always show the active item */
+                        gtk_list_store_set (widget->priv->list_store,
+                                            &iter,
+                                            CHOOSER_ITEM_IS_VISIBLE_COLUMN, TRUE,
+                                            -1);
+                }
+        } while (gtk_tree_model_iter_next (model, &iter));
+
+        gtk_tree_view_set_model (GTK_TREE_VIEW (widget->priv->items_view), view_model);
+        g_object_unref (view_model);
+
+        queue_update_separator_visibility (widget);
+
+ out:
+        g_free (active_item_id);
+}
+
+static void
+on_shrink_animation_complete (GdmScrollableWidget *scrollable_widget,
+                              GdmChooserWidget    *widget)
+{
+        g_assert (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_SHRINKING);
+
+        g_debug ("GdmChooserWidget: shrink complete");
+
+        widget->priv->active_row_normalized_position = 0.5;
+        set_inactive_items_visible (GDM_CHOOSER_WIDGET (widget), FALSE);
+        gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), FALSE);
+        widget->priv->state = GDM_CHOOSER_WIDGET_STATE_SHRUNK;
+
+        queue_update_separator_visibility (widget);
+
+        if (widget->priv->emit_activated_after_resize_animation) {
+                g_signal_emit (widget, signals[ACTIVATED], 0);
+                widget->priv->emit_activated_after_resize_animation = FALSE;
+        }
+}
+
+static int
+get_height_of_row_at_path (GdmChooserWidget *widget,
+                           GtkTreePath      *path)
+{
+        GdkRectangle area;
+
+        gtk_tree_view_get_background_area (GTK_TREE_VIEW (widget->priv->items_view),
+                                           path, NULL, &area);
+
+        return area.height;
+}
+
+static double
+get_normalized_position_of_row_at_path (GdmChooserWidget *widget,
+                                        GtkTreePath      *path)
+{
+        GdkRectangle area_of_row_at_path;
+        GdkRectangle area_of_visible_rows;
+        GtkAllocation items_view_allocation;
+
+        gtk_widget_get_allocation (widget->priv->items_view, &items_view_allocation);
+
+        gtk_tree_view_get_background_area (GTK_TREE_VIEW (widget->priv->items_view),
+                                           path, NULL, &area_of_row_at_path);
+
+        gtk_tree_view_convert_tree_to_widget_coords (GTK_TREE_VIEW (widget->priv->items_view),
+                                                     area_of_visible_rows.x,
+                                                     area_of_visible_rows.y,
+                                                     &area_of_visible_rows.x,
+                                                     &area_of_visible_rows.y);
+        return CLAMP (((double) area_of_row_at_path.y) / items_view_allocation.height, 0.0, 1.0);
+}
+
+static void
+start_shrink_animation (GdmChooserWidget *widget)
+{
+       GtkTreePath *active_row_path;
+       int          active_row_height;
+       int          number_of_visible_rows;
+
+       g_assert (widget->priv->active_row != NULL);
+
+       number_of_visible_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (widget->priv->model_sorter), 
NULL);
+
+       if (number_of_visible_rows <= 1) {
+               on_shrink_animation_complete (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget),
+                                             widget);
+               return;
+       }
+
+       active_row_path = get_view_path_to_active_row (widget);
+       active_row_height = get_height_of_row_at_path (widget, active_row_path);
+       widget->priv->active_row_normalized_position = get_normalized_position_of_row_at_path (widget, 
active_row_path);
+       gtk_tree_path_free (active_row_path);
+
+       gdm_scrollable_widget_slide_to_height (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget),
+                                              active_row_height,
+                                              (GdmScrollableWidgetSlideStepFunc)
+                                              on_shrink_animation_step, widget,
+                                              (GdmScrollableWidgetSlideDoneFunc)
+                                              on_shrink_animation_complete, widget);
+}
+
+static char *
+get_first_item (GdmChooserWidget *widget)
+{
+        GtkTreeModel *model;
+        GtkTreeIter   iter;
+        char         *id;
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        if (!gtk_tree_model_get_iter_first (model, &iter)) {
+                g_assert_not_reached ();
+        }
+
+        gtk_tree_model_get (model, &iter,
+                            CHOOSER_ID_COLUMN, &id, -1);
+        return id;
+}
+
+static gboolean
+activate_if_one_item (GdmChooserWidget *widget)
+{
+        char *id;
+
+        g_debug ("GdmChooserWidget: attempting to activate single item");
+
+        if (gdm_chooser_widget_get_number_of_items (widget) != 1) {
+                g_debug ("GdmChooserWidget: unable to activate single item - has %d items", 
gdm_chooser_widget_get_number_of_items (widget));
+                return FALSE;
+        }
+
+        id = get_first_item (widget);
+        if (id != NULL) {
+                gdm_chooser_widget_set_active_item (widget, id);
+                g_free (id);
+        }
+
+        return FALSE;
+}
+
+static void
+_grab_focus (GtkWidget *widget)
+{
+        GtkWidget *foc_widget;
+
+        foc_widget = GDM_CHOOSER_WIDGET (widget)->priv->items_view;
+        g_debug ("GdmChooserWidget: grabbing focus");
+
+        if (gtk_widget_has_focus (foc_widget)) {
+                g_debug ("GdmChooserWidget: not grabbing focus - already has it");
+                return;
+        }
+
+        gtk_widget_child_focus (foc_widget, GTK_DIR_TAB_FORWARD);
+}
+
+static void
+queue_update_visible_items (GdmChooserWidget *widget)
+{
+        if (widget->priv->update_items_idle_id != 0) {
+                g_source_remove (widget->priv->update_items_idle_id);
+        }
+
+        widget->priv->update_items_idle_id =
+                g_timeout_add (100, (GSourceFunc) update_visible_items, widget);
+}
+
+static void
+on_grow_animation_complete (GdmScrollableWidget *scrollable_widget,
+                            GdmChooserWidget    *widget)
+{
+        g_assert (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWING);
+        widget->priv->state = GDM_CHOOSER_WIDGET_STATE_GROWN;
+        gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), TRUE);
+        queue_update_visible_items (widget);
+
+        _grab_focus (GTK_WIDGET (widget));
+}
+
+static int
+get_number_of_on_screen_rows (GdmChooserWidget *widget)
+{
+        GtkTreePath *start_path;
+        GtkTreePath *end_path;
+        int         *start_index;
+        int         *end_index;
+        int          number_of_rows;
+
+        if (!gtk_tree_view_get_visible_range (GTK_TREE_VIEW (widget->priv->items_view),
+                                              &start_path, &end_path)) {
+                return 0;
+        }
+
+        start_index = gtk_tree_path_get_indices (start_path);
+        end_index = gtk_tree_path_get_indices (end_path);
+
+        number_of_rows = *end_index - *start_index + 1;
+
+        gtk_tree_path_free (start_path);
+        gtk_tree_path_free (end_path);
+
+        return number_of_rows;
+}
+
+static void
+on_grow_animation_step (GdmScrollableWidget *scrollable_widget,
+                        double               progress,
+                        int                 *new_height,
+                        GdmChooserWidget    *widget)
+{
+        int number_of_visible_rows;
+        int number_of_on_screen_rows;
+
+        number_of_visible_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL 
(widget->priv->model_sorter), NULL);
+        number_of_on_screen_rows = get_number_of_on_screen_rows (widget);
+
+        GtkRequisition scrollable_widget_req;
+        gtk_widget_get_requisition (gtk_bin_get_child (GTK_BIN (scrollable_widget)), &scrollable_widget_req);
+        *new_height = scrollable_widget_req.height;
+}
+
+static void
+start_grow_animation (GdmChooserWidget *widget)
+{
+        GtkRequisition scrollable_widget_req;
+        gtk_widget_get_requisition (gtk_bin_get_child (GTK_BIN (widget->priv->scrollable_widget)),
+                                    &scrollable_widget_req);
+
+        set_inactive_items_visible (widget, TRUE);
+
+        gdm_scrollable_widget_slide_to_height (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget),
+                                               scrollable_widget_req.height,
+                                               (GdmScrollableWidgetSlideStepFunc)
+                                               on_grow_animation_step, widget,
+                                               (GdmScrollableWidgetSlideDoneFunc)
+                                               on_grow_animation_complete, widget);
+}
+
+static void
+skip_resize_animation (GdmChooserWidget *widget)
+{
+        if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_SHRINKING) {
+                set_inactive_items_visible (GDM_CHOOSER_WIDGET (widget), FALSE);
+                gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), FALSE);
+                widget->priv->state = GDM_CHOOSER_WIDGET_STATE_SHRUNK;
+        } else if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWING) {
+                set_inactive_items_visible (GDM_CHOOSER_WIDGET (widget), TRUE);
+                gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), TRUE);
+                widget->priv->state = GDM_CHOOSER_WIDGET_STATE_GROWN;
+                _grab_focus (GTK_WIDGET (widget));
+        }
+}
+
+static void
+gdm_chooser_widget_grow (GdmChooserWidget *widget)
+{
+        if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWN) {
+                g_debug ("GdmChooserWidget: Asking for grow but already grown");
+                return;
+        }
+
+        if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_SHRINKING) {
+                gdm_scrollable_widget_stop_sliding (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget));
+        }
+
+        gtk_alignment_set (GTK_ALIGNMENT (widget->priv->frame_alignment),
+                           0.0, 0.0, 1.0, 1.0);
+
+        set_frame_text (widget, widget->priv->inactive_text);
+
+        widget->priv->state = GDM_CHOOSER_WIDGET_STATE_GROWING;
+
+        if (gtk_widget_get_visible (GTK_WIDGET (widget))) {
+                start_grow_animation (widget);
+        } else {
+                skip_resize_animation (widget);
+        }
+}
+
+static gboolean
+move_cursor_to_top (GdmChooserWidget *widget)
+{
+        GtkTreeModel *model;
+        GtkTreePath  *path;
+        GtkTreeIter   iter;
+
+        model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->items_view));
+        path = gtk_tree_path_new_first ();
+        if (gtk_tree_model_get_iter (model, &iter, path)) {
+                gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view),
+                                          path,
+                                          NULL,
+                                          FALSE);
+
+                gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget->priv->items_view),
+                                              path,
+                                              NULL,
+                                              TRUE,
+                                              0.5,
+                                              0.0);
+
+        }
+        gtk_tree_path_free (path);
+
+        widget->priv->update_cursor_idle_id = 0;
+        return FALSE;
+}
+
+static gboolean
+clear_selection (GdmChooserWidget *widget)
+{
+        GtkTreeSelection *selection;
+        GtkWidget        *window;
+
+        g_debug ("GdmChooserWidget: clearing selection");
+
+        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view));
+        gtk_tree_selection_unselect_all (selection);
+
+        window = gtk_widget_get_ancestor (GTK_WIDGET (widget), GTK_TYPE_WINDOW);
+
+        if (window != NULL) {
+                gtk_window_set_focus (GTK_WINDOW (window), NULL);
+        }
+
+        return FALSE;
+}
+
+static void
+gdm_chooser_widget_shrink (GdmChooserWidget *widget)
+{
+        g_assert (widget->priv->should_hide_inactive_items == TRUE);
+
+        if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWING) {
+                gdm_scrollable_widget_stop_sliding (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget));
+        }
+
+        set_frame_text (widget, widget->priv->active_text);
+
+        gtk_alignment_set (GTK_ALIGNMENT (widget->priv->frame_alignment),
+                           0.0, 0.0, 1.0, 0.0);
+
+        clear_selection (widget);
+
+        widget->priv->state = GDM_CHOOSER_WIDGET_STATE_SHRINKING;
+
+        if (gtk_widget_get_visible (GTK_WIDGET (widget))) {
+                start_shrink_animation (widget);
+        } else {
+                skip_resize_animation (widget);
+        }
+}
+
+static void
+activate_from_row (GdmChooserWidget    *widget,
+                   GtkTreeRowReference *row)
+{
+        g_assert (row != NULL);
+        g_assert (gtk_tree_row_reference_valid (row));
+
+        if (widget->priv->active_row != NULL) {
+                gtk_tree_row_reference_free (widget->priv->active_row);
+                widget->priv->active_row = NULL;
+        }
+
+        widget->priv->active_row = gtk_tree_row_reference_copy (row);
+
+        if (widget->priv->should_hide_inactive_items) {
+                g_debug ("GdmChooserWidget: will emit activated after resize");
+                widget->priv->emit_activated_after_resize_animation = TRUE;
+                gdm_chooser_widget_shrink (widget);
+        } else {
+                g_debug ("GdmChooserWidget: emitting activated");
+                g_signal_emit (widget, signals[ACTIVATED], 0);
+        }
+}
+
+static void
+deactivate (GdmChooserWidget *widget)
+{
+        GtkTreePath *path;
+
+        if (widget->priv->active_row == NULL) {
+                return;
+        }
+
+        path = get_view_path_to_active_row (widget);
+
+        gtk_tree_row_reference_free (widget->priv->active_row);
+        widget->priv->active_row = NULL;
+
+        gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view),
+                                  path, NULL, FALSE);
+        gtk_tree_path_free (path);
+
+        if (widget->priv->state != GDM_CHOOSER_WIDGET_STATE_GROWN) {
+                gdm_chooser_widget_grow (widget);
+        } else {
+                queue_update_visible_items (widget);
+        }
+
+        g_signal_emit (widget, signals[DEACTIVATED], 0);
+}
+
+void
+gdm_chooser_widget_activate_selected_item (GdmChooserWidget *widget)
+{
+        GtkTreeRowReference *row;
+        gboolean             is_already_active;
+        GtkTreePath         *path;
+        GtkTreeModel        *model;
+
+        row = NULL;
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+        is_already_active = FALSE;
+
+        path = NULL;
+
+        get_selected_list_path (widget, &path);
+        if (path == NULL) {
+                g_debug ("GdmChooserWidget: no row selected");
+                return;
+        }
+
+        if (widget->priv->active_row != NULL) {
+                GtkTreePath *active_path;
+
+                active_path = gtk_tree_row_reference_get_path (widget->priv->active_row);
+
+                if (gtk_tree_path_compare (path, active_path) == 0) {
+                        is_already_active = TRUE;
+                }
+                gtk_tree_path_free (active_path);
+        }
+        g_assert (path != NULL);
+        row = gtk_tree_row_reference_new (model, path);
+        gtk_tree_path_free (path);
+
+        if (!is_already_active) {
+                activate_from_row (widget, row);
+        } else {
+                g_debug ("GdmChooserWidget: row is already active");
+        }
+        gtk_tree_row_reference_free (row);
+}
+
+void
+gdm_chooser_widget_set_active_item (GdmChooserWidget *widget,
+                                    const char       *id)
+{
+        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
+
+        g_debug ("GdmChooserWidget: setting active item '%s'",
+                 id ? id : "(null)");
+
+        if (id != NULL) {
+                activate_from_item_id (widget, id);
+        } else {
+                deactivate (widget);
+        }
+}
+
+static void
+gdm_chooser_widget_set_property (GObject        *object,
+                                 guint           prop_id,
+                                 const GValue   *value,
+                                 GParamSpec     *pspec)
+{
+        GdmChooserWidget *self;
+
+        self = GDM_CHOOSER_WIDGET (object);
+
+        switch (prop_id) {
+
+        case PROP_INACTIVE_TEXT:
+                g_free (self->priv->inactive_text);
+                self->priv->inactive_text = g_value_dup_string (value);
+
+                if (self->priv->active_row == NULL) {
+                        set_frame_text (self, self->priv->inactive_text);
+                }
+                break;
+
+        case PROP_ACTIVE_TEXT:
+                g_free (self->priv->active_text);
+                self->priv->active_text = g_value_dup_string (value);
+
+                if (self->priv->active_row != NULL) {
+                        set_frame_text (self, self->priv->active_text);
+                }
+                break;
+
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gdm_chooser_widget_get_property (GObject        *object,
+                                 guint           prop_id,
+                                 GValue         *value,
+                                 GParamSpec     *pspec)
+{
+        GdmChooserWidget *self;
+
+        self = GDM_CHOOSER_WIDGET (object);
+
+        switch (prop_id) {
+        case PROP_INACTIVE_TEXT:
+                g_value_set_string (value, self->priv->inactive_text);
+                break;
+
+        case PROP_ACTIVE_TEXT:
+                g_value_set_string (value, self->priv->active_text);
+                break;
+        case PROP_LIST_VISIBLE:
+                g_value_set_boolean (value, self->priv->list_visible);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gdm_chooser_widget_dispose (GObject *object)
+{
+        GdmChooserWidget *widget;
+
+        widget = GDM_CHOOSER_WIDGET (object);
+
+        if (widget->priv->update_items_idle_id > 0) {
+                g_source_remove (widget->priv->update_items_idle_id);
+                widget->priv->update_items_idle_id = 0;
+        }
+
+        if (widget->priv->update_separator_idle_id > 0) {
+                g_source_remove (widget->priv->update_separator_idle_id);
+                widget->priv->update_separator_idle_id = 0;
+        }
+
+        if (widget->priv->update_visibility_idle_id > 0) {
+                g_source_remove (widget->priv->update_visibility_idle_id);
+                widget->priv->update_visibility_idle_id = 0;
+        }
+
+        if (widget->priv->update_cursor_idle_id > 0) {
+                g_source_remove (widget->priv->update_cursor_idle_id);
+                widget->priv->update_cursor_idle_id = 0;
+        }
+
+        if (widget->priv->separator_row != NULL) {
+                gtk_tree_row_reference_free (widget->priv->separator_row);
+                widget->priv->separator_row = NULL;
+        }
+
+        if (widget->priv->active_row != NULL) {
+                gtk_tree_row_reference_free (widget->priv->active_row);
+                widget->priv->active_row = NULL;
+        }
+
+        if (widget->priv->inactive_text != NULL) {
+                g_free (widget->priv->inactive_text);
+                widget->priv->inactive_text = NULL;
+        }
+
+        if (widget->priv->active_text != NULL) {
+                g_free (widget->priv->active_text);
+                widget->priv->active_text = NULL;
+        }
+
+        if (widget->priv->in_use_message != NULL) {
+                g_free (widget->priv->in_use_message);
+                widget->priv->in_use_message = NULL;
+        }
+
+        G_OBJECT_CLASS (gdm_chooser_widget_parent_class)->dispose (object);
+}
+
+static void
+gdm_chooser_widget_hide (GtkWidget *widget)
+{
+        skip_resize_animation (GDM_CHOOSER_WIDGET (widget));
+        GTK_WIDGET_CLASS (gdm_chooser_widget_parent_class)->hide (widget);
+}
+
+static void
+gdm_chooser_widget_show (GtkWidget *widget)
+{
+        skip_resize_animation (GDM_CHOOSER_WIDGET (widget));
+
+        GTK_WIDGET_CLASS (gdm_chooser_widget_parent_class)->show (widget);
+}
+
+static void
+gdm_chooser_widget_map (GtkWidget *widget)
+{
+        queue_update_visible_items (GDM_CHOOSER_WIDGET (widget));
+
+        GTK_WIDGET_CLASS (gdm_chooser_widget_parent_class)->map (widget);
+}
+
+static void
+gdm_chooser_widget_size_allocate (GtkWidget     *widget,
+                                  GtkAllocation *allocation)
+{
+        GdmChooserWidget *chooser_widget;
+
+        GTK_WIDGET_CLASS (gdm_chooser_widget_parent_class)->size_allocate (widget, allocation);
+
+        chooser_widget = GDM_CHOOSER_WIDGET (widget);
+
+}
+
+static gboolean
+gdm_chooser_widget_focus (GtkWidget        *widget,
+                          GtkDirectionType  direction)
+{
+        /* Since we only have one focusable child (the tree view),
+         * no matter which direction we're going the rules are the
+         * same:
+         *
+         *    1) if it's aready got focus, return FALSE to surrender
+         *    that focus.
+         *    2) if it doesn't already have focus, then grab it
+         */
+        if (gtk_container_get_focus_child (GTK_CONTAINER (widget)) != NULL) {
+                g_debug ("GdmChooserWidget: not focusing - focus child not null");
+                return FALSE;
+        }
+
+        _grab_focus (widget);
+
+        return TRUE;
+}
+
+static gboolean
+gdm_chooser_widget_focus_in_event (GtkWidget     *widget,
+                                   GdkEventFocus *focus_event)
+{
+        /* We don't ever want the chooser widget itself to have focus.
+         * Focus should always go to the tree view.
+         */
+        _grab_focus (widget);
+
+        return FALSE;
+}
+
+static void
+gdm_chooser_widget_class_init (GdmChooserWidgetClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+        object_class->get_property = gdm_chooser_widget_get_property;
+        object_class->set_property = gdm_chooser_widget_set_property;
+        object_class->dispose = gdm_chooser_widget_dispose;
+        object_class->finalize = gdm_chooser_widget_finalize;
+        widget_class->size_allocate = gdm_chooser_widget_size_allocate;
+        widget_class->hide = gdm_chooser_widget_hide;
+        widget_class->show = gdm_chooser_widget_show;
+        widget_class->map = gdm_chooser_widget_map;
+        widget_class->focus = gdm_chooser_widget_focus;
+        widget_class->focus_in_event = gdm_chooser_widget_focus_in_event;
+
+        signals [LOADED] = g_signal_new ("loaded",
+                                         G_TYPE_FROM_CLASS (object_class),
+                                         G_SIGNAL_RUN_LAST,
+                                         G_STRUCT_OFFSET (GdmChooserWidgetClass, loaded),
+                                         NULL,
+                                         NULL,
+                                         g_cclosure_marshal_VOID__VOID,
+                                         G_TYPE_NONE,
+                                         0);
+
+        signals [ACTIVATED] = g_signal_new ("activated",
+                                            G_TYPE_FROM_CLASS (object_class),
+                                            G_SIGNAL_RUN_LAST,
+                                            G_STRUCT_OFFSET (GdmChooserWidgetClass, activated),
+                                            NULL,
+                                            NULL,
+                                            g_cclosure_marshal_VOID__VOID,
+                                            G_TYPE_NONE,
+                                            0);
+
+        signals [DEACTIVATED] = g_signal_new ("deactivated",
+                                              G_TYPE_FROM_CLASS (object_class),
+                                              G_SIGNAL_RUN_LAST,
+                                              G_STRUCT_OFFSET (GdmChooserWidgetClass, deactivated),
+                                              NULL,
+                                              NULL,
+                                              g_cclosure_marshal_VOID__VOID,
+                                              G_TYPE_NONE,
+                                              0);
+
+        g_object_class_install_property (object_class,
+                                         PROP_INACTIVE_TEXT,
+                                         g_param_spec_string ("inactive-text",
+                                                              _("Inactive Text"),
+                                                              _("The text to use in the label if the "
+                                                                "user hasn't picked an item yet"),
+                                                              NULL,
+                                                              (G_PARAM_READWRITE |
+                                                               G_PARAM_CONSTRUCT)));
+        g_object_class_install_property (object_class,
+                                         PROP_ACTIVE_TEXT,
+                                         g_param_spec_string ("active-text",
+                                                              _("Active Text"),
+                                                              _("The text to use in the label if the "
+                                                                "user has picked an item"),
+                                                              NULL,
+                                                              (G_PARAM_READWRITE |
+                                                               G_PARAM_CONSTRUCT)));
+
+        g_object_class_install_property (object_class,
+                                         PROP_LIST_VISIBLE,
+                                         g_param_spec_boolean ("list-visible",
+                                                              _("List Visible"),
+                                                              _("Whether the chooser list is visible"),
+                                                              FALSE,
+                                                              G_PARAM_READABLE));
+
+        g_type_class_add_private (klass, sizeof (GdmChooserWidgetPrivate));
+}
+
+static void
+on_row_activated (GtkTreeView          *tree_view,
+                  GtkTreePath          *tree_path,
+                  GtkTreeViewColumn    *tree_column,
+                  GdmChooserWidget     *widget)
+{
+        char *path_str;
+
+        path_str = gtk_tree_path_to_string (tree_path);
+        g_debug ("GdmChooserWidget: row activated '%s'", path_str ? path_str : "(null)");
+        g_free (path_str);
+        gdm_chooser_widget_activate_selected_item (widget);
+}
+
+static gboolean
+path_is_separator (GdmChooserWidget *widget,
+                   GtkTreeModel     *model,
+                   GtkTreePath      *path)
+{
+        GtkTreePath      *separator_path;
+        GtkTreePath      *translated_path;
+        gboolean          is_separator;
+
+        separator_path = gtk_tree_row_reference_get_path (widget->priv->separator_row);
+
+        if (separator_path == NULL) {
+                return FALSE;
+        }
+
+        if (model == GTK_TREE_MODEL (widget->priv->model_sorter)) {
+                GtkTreePath *filtered_path;
+
+                filtered_path = gtk_tree_model_sort_convert_path_to_child_path (widget->priv->model_sorter, 
path);
+
+                translated_path = gtk_tree_model_filter_convert_path_to_child_path 
(widget->priv->model_filter, filtered_path);
+                gtk_tree_path_free (filtered_path);
+        } else if (model == GTK_TREE_MODEL (widget->priv->model_filter)) {
+                translated_path = gtk_tree_model_filter_convert_path_to_child_path 
(widget->priv->model_filter, path);
+        } else {
+                g_assert (model == GTK_TREE_MODEL (widget->priv->list_store));
+                translated_path = gtk_tree_path_copy (path);
+        }
+
+        if (gtk_tree_path_compare (separator_path, translated_path) == 0) {
+                is_separator = TRUE;
+        } else {
+                is_separator = FALSE;
+        }
+        gtk_tree_path_free (translated_path);
+
+        return is_separator;
+}
+
+static int
+gdm_multilingual_collate (const gchar *text_a, const gchar *text_b)
+{
+        PangoDirection          direction_a;
+        PangoDirection          direction_b;
+        const gchar            *p_a;
+        const gchar            *p_b;
+        gchar                  *org_locale = NULL;
+        gchar                  *sub_a;
+        gchar                  *sub_b;
+        gunichar                ch_a;
+        gunichar                ch_b;
+        GUnicodeScript          script_a;
+        GUnicodeScript          script_b;
+        gboolean                composed_alpha_a;
+        gboolean                composed_alpha_b;
+        gboolean                all_alpha_a;
+        gboolean                all_alpha_b;
+        int                     result;
+
+        direction_a = pango_find_base_dir (text_a, -1);
+        direction_b = pango_find_base_dir (text_b, -1);
+        if ((direction_a == PANGO_DIRECTION_LTR ||
+             direction_a == PANGO_DIRECTION_NEUTRAL) &&
+            direction_b == PANGO_DIRECTION_RTL)
+                return -1;
+        if (direction_a == PANGO_DIRECTION_RTL &&
+            (direction_b == PANGO_DIRECTION_LTR ||
+             direction_b == PANGO_DIRECTION_NEUTRAL))
+                return 1;
+
+        if (!g_get_charset (NULL)) {
+                org_locale = g_strdup (setlocale (LC_ALL, NULL));
+                if (!setlocale (LC_ALL, "en_US.UTF-8")) {
+                        setlocale (LC_ALL, org_locale);
+                        g_free (org_locale);
+                        org_locale = NULL;
+                }
+        }
+
+        result = 0;
+        all_alpha_a = all_alpha_b = TRUE;
+        for (p_a = text_a, p_b = text_b;
+             p_a && *p_a && p_b && *p_b;
+             p_a = g_utf8_next_char (p_a), p_b = g_utf8_next_char (p_b)) {
+                ch_a = g_utf8_get_char (p_a);
+                ch_b = g_utf8_get_char (p_b);
+                script_a = g_unichar_get_script (ch_a);
+                script_b = g_unichar_get_script (ch_b);
+                composed_alpha_a = (script_a == G_UNICODE_SCRIPT_LATIN ||
+                                    script_a == G_UNICODE_SCRIPT_GREEK ||
+                                    script_a == G_UNICODE_SCRIPT_CYRILLIC );
+                composed_alpha_b = (script_b == G_UNICODE_SCRIPT_LATIN ||
+                                    script_b == G_UNICODE_SCRIPT_GREEK ||
+                                    script_b == G_UNICODE_SCRIPT_CYRILLIC );
+                all_alpha_a &= composed_alpha_a;
+                all_alpha_b &= composed_alpha_b;
+                if (all_alpha_a && !composed_alpha_b &&
+                    ch_b >= 0x530) {
+                    result = -1;
+                    break;
+                } else if (!composed_alpha_a && all_alpha_b &&
+                           ch_a >= 0x530) {
+                    result = 1;
+                    break;
+                } else if (ch_a != ch_b) {
+                    sub_a = g_strndup (text_a, g_utf8_next_char (p_a) - text_a);
+                    sub_b = g_strndup (text_b, g_utf8_next_char (p_b) - text_b);
+                    result = g_utf8_collate (sub_a, sub_b);
+                    g_free (sub_a);
+                    g_free (sub_b);
+                    if (result != 0) {
+                            break;
+                    }
+                }
+        }
+        if (result != 0) {
+                if (org_locale) {
+                        setlocale (LC_ALL, org_locale);
+                        g_free (org_locale);
+                        org_locale = NULL;
+                }
+                return result;
+        }
+        if (org_locale) {
+                setlocale (LC_ALL, org_locale);
+                g_free (org_locale);
+                org_locale = NULL;
+        }
+        return g_utf8_collate (text_a, text_b);
+}
+
+static int
+compare_item  (GtkTreeModel *model,
+               GtkTreeIter  *a,
+               GtkTreeIter  *b,
+               gpointer      data)
+{
+        GdmChooserWidget *widget;
+        char             *name_a;
+        char             *name_b;
+        gulong            prio_a;
+        gulong            prio_b;
+        gboolean          is_separate_a;
+        gboolean          is_separate_b;
+        int               result;
+        int               direction;
+        GtkTreeIter      *separator_iter;
+        char             *id;
+        PangoAttrList    *attrs;
+
+        g_assert (GDM_IS_CHOOSER_WIDGET (data));
+
+        widget = GDM_CHOOSER_WIDGET (data);
+
+        separator_iter = NULL;
+        if (widget->priv->separator_row != NULL) {
+
+                GtkTreePath      *path_a;
+                GtkTreePath      *path_b;
+
+                path_a = gtk_tree_model_get_path (model, a);
+                path_b = gtk_tree_model_get_path (model, b);
+
+                if (path_is_separator (widget, model, path_a)) {
+                        separator_iter = a;
+                } else if (path_is_separator (widget, model, path_b)) {
+                        separator_iter = b;
+                }
+
+                gtk_tree_path_free (path_a);
+                gtk_tree_path_free (path_b);
+        }
+
+        name_a = NULL;
+        is_separate_a = FALSE;
+        if (separator_iter != a) {
+                gtk_tree_model_get (model, a,
+                                    CHOOSER_NAME_COLUMN, &name_a,
+                                    CHOOSER_PRIORITY_COLUMN, &prio_a,
+                                    CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate_a,
+                                    -1);
+        }
+
+        name_b = NULL;
+        is_separate_b = FALSE;
+        if (separator_iter != b) {
+                gtk_tree_model_get (model, b,
+                                    CHOOSER_NAME_COLUMN, &name_b,
+                                    CHOOSER_ID_COLUMN, &id,
+                                    CHOOSER_PRIORITY_COLUMN, &prio_b,
+                                    CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate_b,
+                                    -1);
+        }
+
+        if (widget->priv->separator_position == GDM_CHOOSER_WIDGET_POSITION_TOP) {
+                direction = -1;
+        } else {
+                direction = 1;
+        }
+
+        if (separator_iter == b) {
+                result = is_separate_a? 1 : -1;
+                result *= direction;
+        } else if (separator_iter == a) {
+                result = is_separate_b? -1 : 1;
+                result *= direction;
+        } else if (is_separate_b == is_separate_a) {
+                if (prio_a == prio_b) {
+                        char *text_a;
+                        char *text_b;
+
+                        text_a = NULL;
+                        text_b = NULL;
+                        pango_parse_markup (name_a, -1, 0, &attrs, &text_a, NULL, NULL);
+                        if (text_a == NULL) {
+                                g_debug ("GdmChooserWidget: unable to parse markup: '%s'", name_a);
+                        }
+                        pango_parse_markup (name_b, -1, 0, &attrs, &text_b, NULL, NULL);
+                        if (text_b == NULL) {
+                                g_debug ("GdmChooserWidget: unable to parse markup: '%s'", name_b);
+                        }
+                        if (text_a != NULL && text_b != NULL) {
+                                result = gdm_multilingual_collate (text_a, text_b);
+                        } else {
+                                result = gdm_multilingual_collate (name_a, name_b);
+                        }
+                        g_free (text_a);
+                        g_free (text_b);
+                } else if (prio_a > prio_b) {
+                        result = -1;
+                } else {
+                        result = 1;
+                }
+        } else {
+                result = is_separate_a - is_separate_b;
+                result *= direction;
+        }
+
+        g_free (name_a);
+        g_free (name_b);
+
+        return result;
+}
+
+static void
+name_cell_data_func (GtkTreeViewColumn  *tree_column,
+                     GtkCellRenderer    *cell,
+                     GtkTreeModel       *model,
+                     GtkTreeIter        *iter,
+                     GdmChooserWidget   *widget)
+{
+        gboolean is_in_use;
+        char    *name;
+        char    *markup;
+
+        name = NULL;
+        gtk_tree_model_get (model,
+                            iter,
+                            CHOOSER_ITEM_IS_IN_USE_COLUMN, &is_in_use,
+                            CHOOSER_NAME_COLUMN, &name,
+                            -1);
+
+        if (is_in_use) {
+                markup = g_strdup_printf ("<b>%s</b>\n"
+                                          "<i><span size=\"x-small\">%s</span></i>",
+                                          name ? name : "(null)", widget->priv->in_use_message);
+        } else {
+                markup = g_strdup_printf ("%s", name ? name : "(null)");
+        }
+        g_free (name);
+
+        g_object_set (cell, "markup", markup, NULL);
+        g_free (markup);
+}
+
+static void
+check_cell_data_func (GtkTreeViewColumn    *tree_column,
+                      GtkCellRenderer      *cell,
+                      GtkTreeModel         *model,
+                      GtkTreeIter          *iter,
+                      GdmChooserWidget     *widget)
+{
+        gboolean   is_in_use;
+        GdkPixbuf *pixbuf;
+
+        gtk_tree_model_get (model,
+                            iter,
+                            CHOOSER_ITEM_IS_IN_USE_COLUMN, &is_in_use,
+                            -1);
+
+        if (is_in_use) {
+                pixbuf = widget->priv->is_in_use_pixbuf;
+        } else {
+                pixbuf = NULL;
+        }
+
+        g_object_set (cell, "pixbuf", pixbuf, NULL);
+}
+
+static GdkPixbuf *
+get_is_in_use_pixbuf (GdmChooserWidget *widget)
+{
+        GtkIconTheme *theme;
+        GdkPixbuf    *pixbuf;
+
+        theme = gtk_icon_theme_get_default ();
+        pixbuf = gtk_icon_theme_load_icon (theme,
+                                           "emblem-default",
+                                           GDM_CHOOSER_WIDGET_DEFAULT_ICON_SIZE / 3,
+                                           0,
+                                           NULL);
+
+        return pixbuf;
+}
+
+static gboolean
+separator_func (GtkTreeModel *model,
+                GtkTreeIter  *iter,
+                gpointer      data)
+{
+        GdmChooserWidget *widget;
+        GtkTreePath      *path;
+        gboolean          is_separator;
+
+        g_assert (GDM_IS_CHOOSER_WIDGET (data));
+
+        widget = GDM_CHOOSER_WIDGET (data);
+
+        g_assert (widget->priv->separator_row != NULL);
+
+        path = gtk_tree_model_get_path (model, iter);
+
+        is_separator = path_is_separator (widget, model, path);
+
+        gtk_tree_path_free (path);
+
+        return is_separator;
+}
+
+static void
+add_separator (GdmChooserWidget *widget)
+{
+        GtkTreeIter   iter;
+        GtkTreeModel *model;
+        GtkTreePath  *path;
+
+        g_assert (widget->priv->separator_row == NULL);
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        gtk_list_store_insert_with_values (widget->priv->list_store,
+                                           &iter, 0,
+                                           CHOOSER_ID_COLUMN, "-", -1);
+        path = gtk_tree_model_get_path (model, &iter);
+        widget->priv->separator_row = gtk_tree_row_reference_new (model, path);
+        gtk_tree_path_free (path);
+}
+
+static gboolean
+update_column_visibility (GdmChooserWidget *widget)
+{
+        g_debug ("GdmChooserWidget: updating column visibility");
+
+        if (widget->priv->number_of_rows_with_images > 0) {
+                gtk_tree_view_column_set_visible (widget->priv->image_column,
+                                                  TRUE);
+        } else {
+                gtk_tree_view_column_set_visible (widget->priv->image_column,
+                                                  FALSE);
+        }
+        if (widget->priv->number_of_rows_with_status > 0) {
+                gtk_tree_view_column_set_visible (widget->priv->status_column,
+                                                  TRUE);
+        } else {
+                gtk_tree_view_column_set_visible (widget->priv->status_column,
+                                                  FALSE);
+        }
+
+        return FALSE;
+}
+
+static void
+clear_canceled_visibility_update (GdmChooserWidget *widget)
+{
+        widget->priv->update_idle_id = 0;
+}
+
+static void
+queue_column_visibility_update (GdmChooserWidget *widget)
+{
+        if (widget->priv->update_idle_id == 0) {
+                widget->priv->update_idle_id =
+                        g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+                                         (GSourceFunc)
+                                         update_column_visibility, widget,
+                                         (GDestroyNotify)
+                                         clear_canceled_visibility_update);
+        }
+}
+
+static void
+on_row_changed (GtkTreeModel     *model,
+                GtkTreePath      *path,
+                GtkTreeIter      *iter,
+                GdmChooserWidget *widget)
+{
+        queue_column_visibility_update (widget);
+}
+
+static void
+add_frame (GdmChooserWidget *widget)
+{
+        widget->priv->frame = gtk_frame_new (NULL);
+        gtk_frame_set_shadow_type (GTK_FRAME (widget->priv->frame),
+                                   GTK_SHADOW_NONE);
+        gtk_widget_show (widget->priv->frame);
+        gtk_container_add (GTK_CONTAINER (widget), widget->priv->frame);
+
+        widget->priv->frame_alignment = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
+        gtk_widget_show (widget->priv->frame_alignment);
+        gtk_container_add (GTK_CONTAINER (widget->priv->frame),
+                           widget->priv->frame_alignment);
+}
+
+static gboolean
+on_button_release (GtkTreeView      *items_view,
+                   GdkEventButton   *event,
+                   GdmChooserWidget *widget)
+{
+        GtkTreeModel     *model;
+        GtkTreeIter       iter;
+        GtkTreeSelection *selection;
+
+        if (!widget->priv->should_hide_inactive_items) {
+                return FALSE;
+        }
+
+        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view));
+        if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+                GtkTreePath *path;
+
+                path = gtk_tree_model_get_path (model, &iter);
+                gtk_tree_view_row_activated (GTK_TREE_VIEW (items_view),
+                                             path, NULL);
+                gtk_tree_path_free (path);
+        }
+
+        return FALSE;
+}
+
+static gboolean
+search_equal_func (GtkTreeModel     *model,
+                   int               column,
+                   const char       *key,
+                   GtkTreeIter      *iter,
+                   GdmChooserWidget *widget)
+{
+        char       *id;
+        char       *name;
+        char       *key_folded;
+        gboolean    ret;
+
+        if (key == NULL) {
+                return FALSE;
+        }
+
+        ret = TRUE;
+        id = NULL;
+        name = NULL;
+
+        key_folded = g_utf8_casefold (key, -1);
+
+        gtk_tree_model_get (model,
+                            iter,
+                            CHOOSER_ID_COLUMN, &id,
+                            CHOOSER_NAME_COLUMN, &name,
+                            -1);
+        if (name != NULL) {
+                char *name_folded;
+
+                name_folded = g_utf8_casefold (name, -1);
+                ret = !g_str_has_prefix (name_folded, key_folded);
+                g_free (name_folded);
+
+                if (!ret) {
+                        goto out;
+                }
+        }
+
+        if (id != NULL) {
+                char *id_folded;
+
+
+                id_folded = g_utf8_casefold (id, -1);
+                ret = !g_str_has_prefix (id_folded, key_folded);
+                g_free (id_folded);
+
+                if (!ret) {
+                        goto out;
+                }
+        }
+ out:
+        g_free (id);
+        g_free (name);
+        g_free (key_folded);
+
+        return ret;
+}
+
+static void
+search_position_func (GtkTreeView *tree_view,
+                      GtkWidget   *search_dialog,
+                      gpointer     user_data)
+{
+        /* Move it outside the region viewable by
+         * the user.
+         * FIXME: This is pretty inelegant.
+         *
+         * It might be nicer to make a GdmOffscreenBin
+         * widget that we pack into the chooser widget below
+         * the frame but gets redirected offscreen.
+         *
+         * Then we would add a GtkEntry to the bin and set
+         * that entry as the search entry for the tree view
+         * instead of using a search position func.
+         */
+        gtk_window_move (GTK_WINDOW (search_dialog), -24000, -24000);
+}
+
+static void
+on_selection_changed (GtkTreeSelection *selection,
+                      GdmChooserWidget *widget)
+{
+        GtkTreePath *path;
+
+        get_selected_list_path (widget, &path);
+        if (path != NULL) {
+                char *path_str;
+                path_str = gtk_tree_path_to_string (path);
+                g_debug ("GdmChooserWidget: selection change to list path '%s'", path_str);
+                g_free (path_str);
+        } else {
+                g_debug ("GdmChooserWidget: selection cleared");
+        }
+}
+
+static void
+on_adjustment_value_changed (GtkAdjustment    *adjustment,
+                             GdmChooserWidget *widget)
+{
+        queue_update_visible_items (widget);
+}
+
+static void
+gdm_chooser_widget_init (GdmChooserWidget *widget)
+{
+        GtkTreeViewColumn *column;
+        GtkTreeSelection  *selection;
+        GtkCellRenderer   *renderer;
+        GtkAdjustment     *adjustment;
+
+        widget->priv = GDM_CHOOSER_WIDGET_GET_PRIVATE (widget);
+
+        /* Even though, we're a container and also don't ever take
+         * focus for ourselve, we set CAN_FOCUS so that gtk_widget_grab_focus
+         * works on us.  We then override grab_focus requests to
+         * be redirected our internal tree view
+         */
+        gtk_widget_set_can_focus (GTK_WIDGET (widget), TRUE);
+
+        gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 0, 0, 0);
+
+        add_frame (widget);
+
+        widget->priv->scrollable_widget = gdm_scrollable_widget_new ();
+        gtk_widget_show (widget->priv->scrollable_widget);
+        gtk_container_add (GTK_CONTAINER (widget->priv->frame_alignment),
+                           widget->priv->scrollable_widget);
+
+        widget->priv->items_view = gtk_tree_view_new ();
+        gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (widget->priv->items_view),
+                                           FALSE);
+        g_signal_connect (widget->priv->items_view,
+                          "row-activated",
+                          G_CALLBACK (on_row_activated),
+                          widget);
+
+        gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (widget->priv->items_view),
+                                             (GtkTreeViewSearchEqualFunc)search_equal_func,
+                                             widget,
+                                             NULL);
+
+        gtk_tree_view_set_search_position_func (GTK_TREE_VIEW (widget->priv->items_view),
+                                                (GtkTreeViewSearchPositionFunc)search_position_func,
+                                                widget,
+                                                NULL);
+
+        /* hack to make single-click activate work
+         */
+        g_signal_connect_after (widget->priv->items_view,
+                               "button-release-event",
+                               G_CALLBACK (on_button_release),
+                               widget);
+
+        gtk_widget_show (widget->priv->items_view);
+        gtk_container_add (GTK_CONTAINER (widget->priv->scrollable_widget),
+                           widget->priv->items_view);
+
+        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view));
+        gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+
+        g_signal_connect (selection, "changed", G_CALLBACK (on_selection_changed), widget);
+
+        g_assert (NUMBER_OF_CHOOSER_COLUMNS == 13);
+        widget->priv->list_store = gtk_list_store_new (NUMBER_OF_CHOOSER_COLUMNS,
+                                                       GDK_TYPE_PIXBUF,
+                                                       G_TYPE_STRING,
+                                                       G_TYPE_STRING,
+                                                       G_TYPE_ULONG,
+                                                       G_TYPE_BOOLEAN,
+                                                       G_TYPE_BOOLEAN,
+                                                       G_TYPE_BOOLEAN,
+                                                       G_TYPE_DOUBLE,
+                                                       G_TYPE_DOUBLE,
+                                                       G_TYPE_DOUBLE,
+                                                       G_TYPE_STRING,
+                                                       G_TYPE_POINTER,
+                                                       G_TYPE_POINTER);
+
+        widget->priv->model_filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL 
(widget->priv->list_store), NULL));
+
+        gtk_tree_model_filter_set_visible_column (widget->priv->model_filter,
+                                                  CHOOSER_ITEM_IS_VISIBLE_COLUMN);
+        g_signal_connect (G_OBJECT (widget->priv->model_filter), "row-changed",
+                          G_CALLBACK (on_row_changed), widget);
+
+        widget->priv->model_sorter = GTK_TREE_MODEL_SORT (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL 
(widget->priv->model_filter)));
+
+        gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (widget->priv->model_sorter),
+                                         CHOOSER_ID_COLUMN,
+                                         compare_item,
+                                         widget, NULL);
+
+        gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (widget->priv->model_sorter),
+                                              CHOOSER_ID_COLUMN,
+                                              GTK_SORT_ASCENDING);
+        gtk_tree_view_set_model (GTK_TREE_VIEW (widget->priv->items_view),
+                                 GTK_TREE_MODEL (widget->priv->model_sorter));
+        gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (widget->priv->items_view),
+                                              separator_func,
+                                              widget, NULL);
+
+        /* IMAGE COLUMN */
+        renderer = gtk_cell_renderer_pixbuf_new ();
+        column = gtk_tree_view_column_new ();
+        gtk_tree_view_column_pack_start (column, renderer, FALSE);
+        gtk_tree_view_append_column (GTK_TREE_VIEW (widget->priv->items_view), column);
+        widget->priv->image_column = column;
+
+        gtk_tree_view_column_set_attributes (column,
+                                             renderer,
+                                             "pixbuf", CHOOSER_IMAGE_COLUMN,
+                                             NULL);
+
+        g_object_set (renderer,
+                      "xalign", 1.0,
+                      NULL);
+
+        /* NAME COLUMN */
+        renderer = gtk_cell_renderer_text_new ();
+        column = gtk_tree_view_column_new ();
+        gtk_tree_view_column_pack_start (column, renderer, FALSE);
+        gtk_tree_view_append_column (GTK_TREE_VIEW (widget->priv->items_view), column);
+        gtk_tree_view_column_set_cell_data_func (column,
+                                                 renderer,
+                                                 (GtkTreeCellDataFunc) name_cell_data_func,
+                                                 widget,
+                                                 NULL);
+
+        gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (widget->priv->items_view),
+                                          CHOOSER_COMMENT_COLUMN);
+
+        /* STATUS COLUMN */
+        renderer = gtk_cell_renderer_pixbuf_new ();
+        column = gtk_tree_view_column_new ();
+        gtk_tree_view_column_pack_start (column, renderer, FALSE);
+        gtk_tree_view_append_column (GTK_TREE_VIEW (widget->priv->items_view), column);
+        widget->priv->status_column = column;
+
+        gtk_tree_view_column_set_cell_data_func (column,
+                                                 renderer,
+                                                 (GtkTreeCellDataFunc) check_cell_data_func,
+                                                 widget,
+                                                 NULL);
+        widget->priv->is_in_use_pixbuf = get_is_in_use_pixbuf (widget);
+
+        renderer = gdm_cell_renderer_timer_new ();
+        gtk_tree_view_column_pack_start (column, renderer, FALSE);
+        gtk_tree_view_column_add_attribute (column, renderer, "value",
+                                            CHOOSER_TIMER_VALUE_COLUMN);
+
+        widget->priv->rows_with_timers =
+            g_hash_table_new_full (g_str_hash,
+                                   g_str_equal,
+                                  (GDestroyNotify) g_free,
+                                  (GDestroyNotify)
+                                  gtk_tree_row_reference_free);
+
+        add_separator (widget);
+        queue_column_visibility_update (widget);
+
+        adjustment = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (widget->priv->items_view));
+        g_signal_connect (adjustment, "value-changed", G_CALLBACK (on_adjustment_value_changed), widget);
+}
+
+static void
+gdm_chooser_widget_finalize (GObject *object)
+{
+        GdmChooserWidget *widget;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (object));
+
+        widget = GDM_CHOOSER_WIDGET (object);
+
+        g_return_if_fail (widget->priv != NULL);
+
+        g_hash_table_destroy (widget->priv->rows_with_timers);
+        widget->priv->rows_with_timers = NULL;
+
+        G_OBJECT_CLASS (gdm_chooser_widget_parent_class)->finalize (object);
+}
+
+GtkWidget *
+gdm_chooser_widget_new (const char *inactive_text,
+                        const char *active_text)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_CHOOSER_WIDGET,
+                               "inactive-text", inactive_text,
+                               "active-text", active_text, NULL);
+
+        return GTK_WIDGET (object);
+}
+
+void
+gdm_chooser_widget_update_item (GdmChooserWidget *widget,
+                                const char       *id,
+                                GdkPixbuf        *new_image,
+                                const char       *new_name,
+                                const char       *new_comment,
+                                gulong            new_priority,
+                                gboolean          new_in_use,
+                                gboolean          new_is_separate)
+{
+        GtkTreeModel *model;
+        GtkTreeIter   iter;
+        GdkPixbuf    *image;
+        gboolean      is_separate;
+        gboolean      in_use;
+
+        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        if (!find_item (widget, id, &iter)) {
+                g_critical ("Tried to remove non-existing item from chooser");
+                return;
+        }
+
+        is_separate = FALSE;
+        gtk_tree_model_get (model, &iter,
+                            CHOOSER_IMAGE_COLUMN, &image,
+                            CHOOSER_ITEM_IS_IN_USE_COLUMN, &in_use,
+                            CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate,
+                            -1);
+
+        if (image != new_image) {
+                if (image == NULL && new_image != NULL) {
+                        widget->priv->number_of_rows_with_images++;
+                } else if (image != NULL && new_image == NULL) {
+                        widget->priv->number_of_rows_with_images--;
+                }
+                queue_column_visibility_update (widget);
+        }
+        if (image != NULL) {
+                g_object_unref (image);
+        }
+
+        if (in_use != new_in_use) {
+                if (new_in_use) {
+                        widget->priv->number_of_rows_with_status++;
+                } else {
+                        widget->priv->number_of_rows_with_status--;
+                }
+                queue_column_visibility_update (widget);
+        }
+
+        if (is_separate != new_is_separate) {
+                if (new_is_separate) {
+                        widget->priv->number_of_separated_rows++;
+                        widget->priv->number_of_normal_rows--;
+                } else {
+                        widget->priv->number_of_separated_rows--;
+                        widget->priv->number_of_normal_rows++;
+                }
+                queue_update_separator_visibility (widget);
+        }
+
+        gtk_list_store_set (widget->priv->list_store,
+                            &iter,
+                            CHOOSER_IMAGE_COLUMN, new_image,
+                            CHOOSER_NAME_COLUMN, new_name,
+                            CHOOSER_COMMENT_COLUMN, new_comment,
+                            CHOOSER_PRIORITY_COLUMN, new_priority,
+                            CHOOSER_ITEM_IS_IN_USE_COLUMN, new_in_use,
+                            CHOOSER_ITEM_IS_SEPARATED_COLUMN, new_is_separate,
+                            -1);
+}
+
+static void
+queue_update_chooser_visibility (GdmChooserWidget *widget)
+{
+        if (widget->priv->update_visibility_idle_id == 0) {
+                widget->priv->update_visibility_idle_id =
+                        g_idle_add ((GSourceFunc) update_chooser_visibility, widget);
+        }
+}
+
+static void
+queue_move_cursor_to_top (GdmChooserWidget *widget)
+{
+        if (widget->priv->update_cursor_idle_id == 0) {
+                widget->priv->update_cursor_idle_id =
+                        g_idle_add ((GSourceFunc) move_cursor_to_top, widget);
+        }
+}
+
+void
+gdm_chooser_widget_add_item (GdmChooserWidget *widget,
+                             const char       *id,
+                             GdkPixbuf        *image,
+                             const char       *name,
+                             const char       *comment,
+                             gulong            priority,
+                             gboolean          in_use,
+                             gboolean          keep_separate,
+                             GdmChooserWidgetItemLoadFunc load_func,
+                             gpointer                     load_data)
+{
+        gboolean is_visible;
+
+        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
+
+        if (keep_separate) {
+                widget->priv->number_of_separated_rows++;
+        } else {
+                widget->priv->number_of_normal_rows++;
+        }
+        queue_update_separator_visibility (widget);
+
+        if (in_use) {
+                widget->priv->number_of_rows_with_status++;
+                queue_column_visibility_update (widget);
+        }
+
+        if (image != NULL) {
+                widget->priv->number_of_rows_with_images++;
+                queue_column_visibility_update (widget);
+        }
+
+        is_visible = widget->priv->active_row == NULL;
+
+        gtk_list_store_insert_with_values (widget->priv->list_store,
+                                           NULL, 0,
+                                           CHOOSER_IMAGE_COLUMN, image,
+                                           CHOOSER_NAME_COLUMN, name,
+                                           CHOOSER_COMMENT_COLUMN, comment,
+                                           CHOOSER_PRIORITY_COLUMN, priority,
+                                           CHOOSER_ITEM_IS_IN_USE_COLUMN, in_use,
+                                           CHOOSER_ITEM_IS_SEPARATED_COLUMN, keep_separate,
+                                           CHOOSER_ITEM_IS_VISIBLE_COLUMN, is_visible,
+                                           CHOOSER_ID_COLUMN, id,
+                                           CHOOSER_LOAD_FUNC_COLUMN, load_func,
+                                           CHOOSER_LOAD_DATA_COLUMN, load_data,
+                                           -1);
+
+        queue_update_chooser_visibility (widget);
+}
+
+void
+gdm_chooser_widget_remove_item (GdmChooserWidget *widget,
+                                const char       *id)
+{
+        GtkTreeModel *model;
+        GtkTreeIter   iter;
+        GdkPixbuf    *image;
+        gboolean      is_separate;
+        gboolean      is_in_use;
+
+        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        if (!find_item (widget, id, &iter)) {
+                g_critical ("Tried to remove non-existing item from chooser");
+                return;
+        }
+
+        is_separate = FALSE;
+        gtk_tree_model_get (model, &iter,
+                            CHOOSER_IMAGE_COLUMN, &image,
+                            CHOOSER_ITEM_IS_IN_USE_COLUMN, &is_in_use,
+                            CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate,
+                            -1);
+
+        if (image != NULL) {
+                widget->priv->number_of_rows_with_images--;
+                g_object_unref (image);
+        }
+
+        if (is_in_use) {
+                widget->priv->number_of_rows_with_status--;
+                queue_column_visibility_update (widget);
+        }
+
+        if (is_separate) {
+                widget->priv->number_of_separated_rows--;
+        } else {
+                widget->priv->number_of_normal_rows--;
+        }
+        queue_update_separator_visibility (widget);
+
+        gtk_list_store_remove (widget->priv->list_store, &iter);
+
+        queue_update_chooser_visibility (widget);
+}
+
+gboolean
+gdm_chooser_widget_lookup_item (GdmChooserWidget *widget,
+                                const char       *id,
+                                GdkPixbuf       **image,
+                                char            **name,
+                                char            **comment,
+                                gulong           *priority,
+                                gboolean         *is_in_use,
+                                gboolean         *is_separate)
+{
+        GtkTreeIter   iter;
+        char         *active_item_id;
+
+        g_return_val_if_fail (GDM_IS_CHOOSER_WIDGET (widget), FALSE);
+        g_return_val_if_fail (id != NULL, FALSE);
+
+        active_item_id = get_active_item_id (widget, &iter);
+
+        if (active_item_id == NULL || strcmp (active_item_id, id) != 0) {
+                g_free (active_item_id);
+                active_item_id = NULL;
+
+                if (!find_item (widget, id, &iter)) {
+                        return FALSE;
+                }
+        }
+        g_free (active_item_id);
+
+        if (image != NULL) {
+                gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter,
+                                    CHOOSER_IMAGE_COLUMN, image, -1);
+        }
+
+        if (name != NULL) {
+                gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter,
+                                    CHOOSER_NAME_COLUMN, name, -1);
+        }
+
+        if (priority != NULL) {
+                gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter,
+                                    CHOOSER_PRIORITY_COLUMN, priority, -1);
+        }
+
+        if (is_in_use != NULL) {
+                gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter,
+                                    CHOOSER_ITEM_IS_IN_USE_COLUMN, is_in_use, -1);
+        }
+
+        if (is_separate != NULL) {
+                gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter,
+                                    CHOOSER_ITEM_IS_SEPARATED_COLUMN, is_separate, -1);
+        }
+
+        return TRUE;
+}
+
+void
+gdm_chooser_widget_set_item_in_use (GdmChooserWidget *widget,
+                                    const char       *id,
+                                    gboolean          is_in_use)
+{
+        GtkTreeIter   iter;
+        gboolean      was_in_use;
+
+        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
+
+        if (!find_item (widget, id, &iter)) {
+                return;
+        }
+
+        gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter,
+                            CHOOSER_ITEM_IS_IN_USE_COLUMN, &was_in_use,
+                            -1);
+
+        if (was_in_use != is_in_use) {
+
+                if (is_in_use) {
+                        widget->priv->number_of_rows_with_status++;
+                } else {
+                        widget->priv->number_of_rows_with_status--;
+                }
+                queue_column_visibility_update (widget);
+
+                gtk_list_store_set (widget->priv->list_store,
+                                    &iter,
+                                    CHOOSER_ITEM_IS_IN_USE_COLUMN, is_in_use,
+                                    -1);
+
+        }
+}
+
+void
+gdm_chooser_widget_set_item_priority (GdmChooserWidget *widget,
+                                      const char       *id,
+                                      gulong            priority)
+{
+        GtkTreeIter   iter;
+        gulong        was_priority;
+
+        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
+
+        if (!find_item (widget, id, &iter)) {
+                return;
+        }
+
+        gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter,
+                            CHOOSER_PRIORITY_COLUMN, &was_priority,
+                            -1);
+
+        if (was_priority != priority) {
+
+                gtk_list_store_set (widget->priv->list_store,
+                                    &iter,
+                                    CHOOSER_PRIORITY_COLUMN, priority,
+                                    -1);
+                gtk_tree_model_filter_refilter (widget->priv->model_filter);
+        }
+}
+
+static double
+get_current_time (void)
+{
+  const double microseconds_per_second = 1000000.0;
+  double       timestamp;
+  GTimeVal     now;
+
+  g_get_current_time (&now);
+
+  timestamp = ((microseconds_per_second * now.tv_sec) + now.tv_usec) /
+               microseconds_per_second;
+
+  return timestamp;
+}
+
+static gboolean
+on_timer_timeout (GdmChooserWidget *widget)
+{
+        GHashTableIter  iter;
+        GSList         *list;
+        GSList         *tmp;
+        gpointer        key;
+        gpointer        value;
+        double          now;
+
+        list = NULL;
+        g_hash_table_iter_init (&iter, widget->priv->rows_with_timers);
+        while (g_hash_table_iter_next (&iter, &key, &value)) {
+                list = g_slist_prepend (list, value);
+        }
+
+        now = get_current_time ();
+        for (tmp = list; tmp != NULL; tmp = tmp->next) {
+                GtkTreeRowReference *row;
+
+                row = (GtkTreeRowReference *) tmp->data;
+
+                update_timer_from_time (widget, row, now);
+        }
+        g_slist_free (list);
+
+        return TRUE;
+}
+
+static void
+start_timer (GdmChooserWidget    *widget,
+             GtkTreeRowReference *row,
+             double               duration)
+{
+        GtkTreeModel *model;
+        GtkTreePath  *path;
+        GtkTreeIter   iter;
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        path = gtk_tree_row_reference_get_path (row);
+        gtk_tree_model_get_iter (model, &iter, path);
+        gtk_tree_path_free (path);
+
+        gtk_list_store_set (widget->priv->list_store, &iter,
+                            CHOOSER_TIMER_START_TIME_COLUMN,
+                            get_current_time (), -1);
+        gtk_list_store_set (widget->priv->list_store, &iter,
+                            CHOOSER_TIMER_DURATION_COLUMN,
+                            duration, -1);
+        gtk_list_store_set (widget->priv->list_store, &iter,
+                            CHOOSER_TIMER_VALUE_COLUMN, 0.0, -1);
+
+        widget->priv->number_of_active_timers++;
+        if (widget->priv->timer_animation_timeout_id == 0) {
+                g_assert (g_hash_table_size (widget->priv->rows_with_timers) == 1);
+
+                widget->priv->timer_animation_timeout_id =
+                    g_timeout_add (1000 / 20,
+                                   (GSourceFunc) on_timer_timeout,
+                                   widget);
+
+        }
+}
+
+static void
+stop_timer (GdmChooserWidget    *widget,
+            GtkTreeRowReference *row)
+{
+        GtkTreeModel *model;
+        GtkTreePath  *path;
+        GtkTreeIter   iter;
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        path = gtk_tree_row_reference_get_path (row);
+        gtk_tree_model_get_iter (model, &iter, path);
+        gtk_tree_path_free (path);
+
+        gtk_list_store_set (widget->priv->list_store, &iter,
+                            CHOOSER_TIMER_START_TIME_COLUMN,
+                            0.0, -1);
+        gtk_list_store_set (widget->priv->list_store, &iter,
+                            CHOOSER_TIMER_DURATION_COLUMN,
+                            0.0, -1);
+        gtk_list_store_set (widget->priv->list_store, &iter,
+                            CHOOSER_TIMER_VALUE_COLUMN, 0.0, -1);
+
+        widget->priv->number_of_active_timers--;
+        if (widget->priv->number_of_active_timers == 0) {
+                g_source_remove (widget->priv->timer_animation_timeout_id);
+                widget->priv->timer_animation_timeout_id = 0;
+        }
+}
+
+static void
+update_timer_from_time (GdmChooserWidget    *widget,
+                        GtkTreeRowReference *row,
+                        double               now)
+{
+        GtkTreeModel *model;
+        GtkTreePath  *path;
+        GtkTreeIter   iter;
+        double        start_time;
+        double        duration;
+        double        elapsed_ratio;
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        path = gtk_tree_row_reference_get_path (row);
+        gtk_tree_model_get_iter (model, &iter, path);
+        gtk_tree_path_free (path);
+
+        gtk_tree_model_get (model, &iter,
+                            CHOOSER_TIMER_START_TIME_COLUMN, &start_time,
+                            CHOOSER_TIMER_DURATION_COLUMN, &duration, -1);
+
+        if (duration > G_MINDOUBLE) {
+                elapsed_ratio = (now - start_time) / duration;
+        } else {
+                elapsed_ratio = 0.0;
+        }
+
+        gtk_list_store_set (widget->priv->list_store, &iter,
+                            CHOOSER_TIMER_VALUE_COLUMN,
+                            elapsed_ratio, -1);
+
+        if (elapsed_ratio > .999) {
+                char *id;
+
+                stop_timer (widget, row);
+
+                gtk_tree_model_get (model, &iter,
+                                    CHOOSER_ID_COLUMN, &id, -1);
+                g_hash_table_remove (widget->priv->rows_with_timers,
+                                     id);
+                g_free (id);
+
+                widget->priv->number_of_rows_with_status--;
+                queue_column_visibility_update (widget);
+        }
+}
+
+void
+gdm_chooser_widget_set_item_timer (GdmChooserWidget *widget,
+                                   const char       *id,
+                                   gulong            timeout)
+{
+        GtkTreeModel        *model;
+        GtkTreeRowReference *row;
+
+        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        row = g_hash_table_lookup (widget->priv->rows_with_timers,
+                                   id);
+
+        g_assert (row == NULL || gtk_tree_row_reference_valid (row));
+
+        if (row != NULL) {
+                stop_timer (widget, row);
+        }
+
+        if (timeout == 0) {
+                if (row == NULL) {
+                        g_warning ("could not find item with ID '%s' to "
+                                   "remove timer", id);
+                        return;
+                }
+
+                g_hash_table_remove (widget->priv->rows_with_timers,
+                                     id);
+                gtk_tree_model_filter_refilter (widget->priv->model_filter);
+                return;
+        }
+
+        if (row == NULL) {
+                GtkTreeIter  iter;
+                GtkTreePath *path;
+
+                if (!find_item (widget, id, &iter)) {
+                        g_warning ("could not find item with ID '%s' to "
+                                   "add timer", id);
+                        return;
+                }
+
+                path = gtk_tree_model_get_path (model, &iter);
+                row = gtk_tree_row_reference_new (model, path);
+
+                g_hash_table_insert (widget->priv->rows_with_timers,
+                                     g_strdup (id), row);
+
+                widget->priv->number_of_rows_with_status++;
+                queue_column_visibility_update (widget);
+        }
+
+        start_timer (widget, row, timeout / 1000.0);
+}
+
+void
+gdm_chooser_widget_set_in_use_message (GdmChooserWidget *widget,
+                                       const char       *message)
+{
+        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
+
+        g_free (widget->priv->in_use_message);
+        widget->priv->in_use_message = g_strdup (message);
+}
+
+void
+gdm_chooser_widget_set_separator_position (GdmChooserWidget         *widget,
+                                           GdmChooserWidgetPosition  position)
+{
+        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
+
+        if (widget->priv->separator_position != position) {
+                widget->priv->separator_position = position;
+        }
+
+        gtk_tree_model_filter_refilter (widget->priv->model_filter);
+}
+
+void
+gdm_chooser_widget_set_hide_inactive_items (GdmChooserWidget  *widget,
+                                            gboolean           should_hide)
+{
+        widget->priv->should_hide_inactive_items = should_hide;
+
+        if (should_hide &&
+            (widget->priv->state != GDM_CHOOSER_WIDGET_STATE_SHRUNK
+             || widget->priv->state != GDM_CHOOSER_WIDGET_STATE_SHRINKING) &&
+            widget->priv->active_row != NULL) {
+                gdm_chooser_widget_shrink (widget);
+        } else if (!should_hide &&
+              (widget->priv->state != GDM_CHOOSER_WIDGET_STATE_GROWN
+               || widget->priv->state != GDM_CHOOSER_WIDGET_STATE_GROWING)) {
+                gdm_chooser_widget_grow (widget);
+        }
+}
+
+int
+gdm_chooser_widget_get_number_of_items (GdmChooserWidget *widget)
+{
+        return widget->priv->number_of_normal_rows +
+               widget->priv->number_of_separated_rows;
+}
+
+void
+gdm_chooser_widget_activate_if_one_item (GdmChooserWidget *widget)
+{
+        activate_if_one_item (widget);
+}
+
+void
+gdm_chooser_widget_propagate_pending_key_events (GdmChooserWidget *widget)
+{
+        if (!gdm_scrollable_widget_has_queued_key_events (GDM_SCROLLABLE_WIDGET 
(widget->priv->scrollable_widget))) {
+                return;
+        }
+
+        gdm_scrollable_widget_replay_queued_key_events (GDM_SCROLLABLE_WIDGET 
(widget->priv->scrollable_widget));
+}
+
+void
+gdm_chooser_widget_loaded (GdmChooserWidget *widget)
+{
+        g_signal_emit (widget, signals[LOADED], 0);
+        update_chooser_visibility (widget);
+        queue_move_cursor_to_top (widget);
+}
diff --git a/gui/simple-greeter/gdm-chooser-widget.h b/gui/simple-greeter/gdm-chooser-widget.h
new file mode 100644
index 0000000..d36e7ed
--- /dev/null
+++ b/gui/simple-greeter/gdm-chooser-widget.h
@@ -0,0 +1,151 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 Ray Strode <rstrode redhat com>
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef __GDM_CHOOSER_WIDGET_H
+#define __GDM_CHOOSER_WIDGET_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_CHOOSER_WIDGET         (gdm_chooser_widget_get_type ())
+#define GDM_CHOOSER_WIDGET(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_CHOOSER_WIDGET, 
GdmChooserWidget))
+#define GDM_CHOOSER_WIDGET_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_CHOOSER_WIDGET, 
GdmChooserWidgetClass))
+#define GDM_IS_CHOOSER_WIDGET(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_CHOOSER_WIDGET))
+#define GDM_IS_CHOOSER_WIDGET_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_CHOOSER_WIDGET))
+#define GDM_CHOOSER_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_CHOOSER_WIDGET, 
GdmChooserWidgetClass))
+
+typedef struct GdmChooserWidgetPrivate GdmChooserWidgetPrivate;
+
+typedef struct
+{
+        GtkAlignment             parent;
+        GdmChooserWidgetPrivate *priv;
+} GdmChooserWidget;
+
+typedef struct
+{
+        GtkAlignmentClass       parent_class;
+
+        void (* loaded)         (GdmChooserWidget *widget);
+
+        void (* activated)      (GdmChooserWidget *widget);
+        void (* deactivated)    (GdmChooserWidget *widget);
+} GdmChooserWidgetClass;
+
+typedef enum {
+        GDM_CHOOSER_WIDGET_POSITION_TOP = 0,
+        GDM_CHOOSER_WIDGET_POSITION_BOTTOM,
+} GdmChooserWidgetPosition;
+
+typedef void     (*GdmChooserWidgetItemLoadFunc)         (GdmChooserWidget *widget,
+                                                          const char       *id,
+                                                          gpointer          data);
+
+typedef gboolean (*GdmChooserUpdateForeachFunc)          (GdmChooserWidget *widget,
+                                                          const char       *id,
+                                                          GdkPixbuf       **image,
+                                                          char            **name,
+                                                          char            **comment,
+                                                          gulong           *priority,
+                                                          gboolean         *is_in_use,
+                                                          gboolean         *is_separate,
+                                                          gpointer          data);
+
+GType        gdm_chooser_widget_get_type                     (void);
+GtkWidget *  gdm_chooser_widget_new                          (const char       *unactive_label,
+                                                              const char       *active_label);
+
+void         gdm_chooser_widget_add_item                     (GdmChooserWidget *widget,
+                                                              const char       *id,
+                                                              GdkPixbuf        *image,
+                                                              const char       *name,
+                                                              const char       *comment,
+                                                              gulong            priority,
+                                                              gboolean          is_in_use,
+                                                              gboolean          keep_separate,
+                                                              GdmChooserWidgetItemLoadFunc load_func,
+                                                              gpointer                     load_data);
+
+void         gdm_chooser_widget_update_foreach_item          (GdmChooserWidget           *widget,
+                                                              GdmChooserUpdateForeachFunc cb,
+                                                              gpointer                    data);
+
+void         gdm_chooser_widget_update_item                  (GdmChooserWidget *widget,
+                                                              const char       *id,
+                                                              GdkPixbuf        *new_image,
+                                                              const char       *new_name,
+                                                              const char       *new_comment,
+                                                              gulong            priority,
+                                                              gboolean          new_in_use,
+                                                              gboolean          new_is_separate);
+
+void          gdm_chooser_widget_remove_item                  (GdmChooserWidget *widget,
+                                                               const char       *id);
+
+gboolean      gdm_chooser_widget_lookup_item                  (GdmChooserWidget           *widget,
+                                                               const char                 *id,
+                                                               GdkPixbuf                 **image,
+                                                               char                      **name,
+                                                               char                      **comment,
+                                                               gulong                     *priority,
+                                                               gboolean                   *is_in_use,
+                                                               gboolean                   *is_separate);
+
+char *         gdm_chooser_widget_get_selected_item            (GdmChooserWidget          *widget);
+void           gdm_chooser_widget_set_selected_item            (GdmChooserWidget          *widget,
+                                                                const char                *item);
+
+char *         gdm_chooser_widget_get_active_item              (GdmChooserWidget          *widget);
+void           gdm_chooser_widget_set_active_item              (GdmChooserWidget          *widget,
+                                                                const char                *item);
+
+void           gdm_chooser_widget_set_item_in_use              (GdmChooserWidget          *widget,
+                                                                const char                *id,
+                                                                gboolean                   is_in_use);
+void           gdm_chooser_widget_set_item_priority            (GdmChooserWidget          *widget,
+                                                                const char                *id,
+                                                                gulong                     priority);
+void           gdm_chooser_widget_set_item_timer               (GdmChooserWidget          *widget,
+                                                                const char                *id,
+                                                                gulong                     timeout);
+void           gdm_chooser_widget_set_in_use_message           (GdmChooserWidget          *widget,
+                                                                const char                *message);
+
+void           gdm_chooser_widget_set_separator_position        (GdmChooserWidget         *widget,
+                                                                 GdmChooserWidgetPosition  position);
+void           gdm_chooser_widget_set_hide_inactive_items       (GdmChooserWidget         *widget,
+                                                                 gboolean                  should_hide);
+
+void           gdm_chooser_widget_activate_selected_item       (GdmChooserWidget          *widget);
+
+int            gdm_chooser_widget_get_number_of_items          (GdmChooserWidget          *widget);
+void           gdm_chooser_widget_activate_if_one_item         (GdmChooserWidget          *widget);
+void           gdm_chooser_widget_propagate_pending_key_events (GdmChooserWidget          *widget);
+
+/* Protected
+ */
+void           gdm_chooser_widget_loaded                       (GdmChooserWidget          *widget);
+
+G_END_DECLS
+
+#endif /* __GDM_CHOOSER_WIDGET_H */
diff --git a/gui/simple-greeter/gdm-clock-widget.c b/gui/simple-greeter/gdm-clock-widget.c
new file mode 100644
index 0000000..0c6fbde
--- /dev/null
+++ b/gui/simple-greeter/gdm-clock-widget.c
@@ -0,0 +1,314 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Written by: William Jon McCann <mccann jhu edu>
+ *             Ray Strode <rstrode redhat com>
+ */
+
+#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 "gdm-clock-widget.h"
+
+#define GDM_CLOCK_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_CLOCK_WIDGET, 
GdmClockWidgetPrivate))
+
+struct GdmClockWidgetPrivate
+{
+        GtkWidget *label;
+        char      *time_format;
+        char      *tooltip_format;
+        guint      update_clock_id;
+        guint      should_show_seconds : 1;
+        guint      should_show_date : 1;
+};
+
+static void     gdm_clock_widget_class_init  (GdmClockWidgetClass *klass);
+static void     gdm_clock_widget_init        (GdmClockWidget      *clock_widget);
+static void     gdm_clock_widget_finalize    (GObject             *object);
+static gboolean update_timeout_cb            (GdmClockWidget      *clock);
+
+G_DEFINE_TYPE (GdmClockWidget, gdm_clock_widget, GTK_TYPE_ALIGNMENT)
+
+static void
+update_time_format (GdmClockWidget *clock)
+{
+        char       *clock_format;
+        char       *tooltip_format;
+
+        if (clock->priv->should_show_date && clock->priv->should_show_seconds) {
+                /* translators: This is the time format to use when both
+                 * the date and time with seconds are being shown together.
+                 */
+                clock_format = _("%a %b %e, %l:%M:%S %p");
+                tooltip_format = NULL;
+        } else if (clock->priv->should_show_date && !clock->priv->should_show_seconds) {
+                /* translators: This is the time format to use when both
+                 * the date and time without seconds are being shown together.
+                 */
+                clock_format = _("%a %b %e, %l:%M %p");
+
+                tooltip_format = NULL;
+        } else if (!clock->priv->should_show_date && clock->priv->should_show_seconds) {
+                /* translators: This is the time format to use when there is
+                 * no date, just weekday and time with seconds.
+                 */
+                clock_format = _("%a %l:%M:%S %p");
+
+                /* translators: This is the time format to use for the date
+                 */
+                tooltip_format = "%x";
+        } else {
+                /* translators: This is the time format to use when there is
+                 * no date, just weekday and time without seconds.
+                 */
+                clock_format = _("%a %l:%M %p");
+
+                tooltip_format = "%x";
+        }
+
+        g_free (clock->priv->time_format);
+        clock->priv->time_format = g_locale_from_utf8 (clock_format, -1, NULL, NULL, NULL);
+
+        g_free (clock->priv->tooltip_format);
+
+        if (tooltip_format != NULL) {
+                clock->priv->tooltip_format = g_locale_from_utf8 (tooltip_format, -1, NULL, NULL, NULL);
+        } else {
+                clock->priv->tooltip_format = NULL;
+        }
+}
+
+static void
+update_clock (GtkLabel   *label,
+              const char *clock_format,
+              const char *tooltip_format)
+{
+        time_t     t;
+        struct tm *tm;
+        char       buf[256];
+        char      *utf8;
+        char      *markup;
+
+        time (&t);
+        tm = localtime (&t);
+        if (tm == NULL) {
+                g_warning ("Unable to get broken down local time");
+                return;
+        }
+        if (strftime (buf, sizeof (buf), clock_format, tm) == 0) {
+                g_warning ("Couldn't format time: %s", clock_format);
+                strcpy (buf, "???");
+        }
+        utf8 = g_locale_to_utf8 (buf, -1, NULL, NULL, NULL);
+        markup = g_strdup_printf ("<b><span foreground=\"white\">%s</span></b>", utf8);
+        gtk_label_set_markup (label, markup);
+        g_free (markup);
+        g_free (utf8);
+
+        if (tooltip_format != NULL) {
+                if (strftime (buf, sizeof (buf), tooltip_format, tm) == 0) {
+                        g_warning ("Couldn't format tooltip date: %s", tooltip_format);
+                        strcpy (buf, "???");
+                }
+                utf8 = g_locale_to_utf8 (buf, -1, NULL, NULL, NULL);
+                gtk_widget_set_tooltip_text (GTK_WIDGET (label), utf8);
+                g_free (utf8);
+        } else {
+                gtk_widget_set_has_tooltip (GTK_WIDGET (label), FALSE);
+        }
+}
+
+static void
+set_clock_timeout (GdmClockWidget *clock,
+                   time_t          now)
+{
+        GTimeVal       tv;
+        int            timeouttime;
+
+        if (clock->priv->update_clock_id > 0) {
+                g_source_remove (clock->priv->update_clock_id);
+                clock->priv->update_clock_id = 0;
+        }
+
+        g_get_current_time (&tv);
+        timeouttime = (G_USEC_PER_SEC - tv.tv_usec) / 1000 + 1;
+
+        /* timeout of one minute if we don't care about the seconds */
+        if (! clock->priv->should_show_seconds) {
+                timeouttime += 1000 * (59 - now % 60);
+        }
+
+        clock->priv->update_clock_id = g_timeout_add (timeouttime,
+                                                      (GSourceFunc)update_timeout_cb,
+                                                      clock);
+}
+
+static gboolean
+update_timeout_cb (GdmClockWidget *clock)
+{
+        time_t     new_time;
+
+        time (&new_time);
+
+        if (clock->priv->label != NULL) {
+                update_clock (GTK_LABEL (clock->priv->label),
+                              clock->priv->time_format,
+                              clock->priv->tooltip_format);
+        }
+
+        set_clock_timeout (clock, new_time);
+
+        return FALSE;
+}
+
+static void
+remove_timeout (GdmClockWidget *clock)
+{
+        if (clock->priv->update_clock_id > 0) {
+                g_source_remove (clock->priv->update_clock_id);
+                clock->priv->update_clock_id = 0;
+        }
+}
+
+static void
+gdm_clock_widget_get_preferred_width (GtkWidget *widget,
+                                      gint      *minimum_size,
+                                      gint      *natural_size)
+{
+        if (GTK_WIDGET_CLASS (gdm_clock_widget_parent_class)->get_preferred_width) {
+                GTK_WIDGET_CLASS (gdm_clock_widget_parent_class)->get_preferred_width (widget, minimum_size, 
natural_size);
+        }
+
+}
+
+static void
+gdm_clock_widget_get_preferred_height (GtkWidget *widget,
+                                       gint      *minimum_size,
+                                       gint      *natural_size)
+{
+        PangoFontMetrics *metrics;
+        PangoContext     *context;
+        int               ascent;
+        int               descent;
+        int               padding;
+        int               min_size;
+        int               nat_size;
+
+        min_size = 0;
+        nat_size = 0;
+
+        if (GTK_WIDGET_CLASS (gdm_clock_widget_parent_class)->get_preferred_height) {
+                GTK_WIDGET_CLASS (gdm_clock_widget_parent_class)->get_preferred_height (widget, &min_size, 
&nat_size);
+        }
+
+        gtk_widget_ensure_style (widget);
+        context = gtk_widget_get_pango_context (widget);
+        metrics = pango_context_get_metrics (context,
+                                             gtk_widget_get_style (widget)->font_desc,
+                                             pango_context_get_language (context));
+
+        ascent = pango_font_metrics_get_ascent (metrics);
+        descent = pango_font_metrics_get_descent (metrics);
+        padding = PANGO_PIXELS (ascent + descent) / 2.0;
+        min_size += padding;
+        nat_size += padding;
+
+        if (minimum_size)
+                *minimum_size = min_size;
+        if (natural_size)
+                *natural_size = nat_size;
+
+        pango_font_metrics_unref (metrics);
+}
+
+static void
+gdm_clock_widget_class_init (GdmClockWidgetClass *klass)
+{
+        GObjectClass *object_class;
+        GtkWidgetClass *widget_class;
+
+        object_class = G_OBJECT_CLASS (klass);
+        widget_class = GTK_WIDGET_CLASS (klass);
+
+        object_class->finalize = gdm_clock_widget_finalize;
+        widget_class->get_preferred_width = gdm_clock_widget_get_preferred_width;
+        widget_class->get_preferred_height = gdm_clock_widget_get_preferred_height;
+
+        g_type_class_add_private (klass, sizeof (GdmClockWidgetPrivate));
+}
+
+static void
+gdm_clock_widget_init (GdmClockWidget *widget)
+{
+        GtkWidget *box;
+
+        widget->priv = GDM_CLOCK_WIDGET_GET_PRIVATE (widget);
+
+        box = gtk_hbox_new (FALSE, 6);
+        gtk_widget_show (box);
+        gtk_container_add (GTK_CONTAINER (widget), box);
+
+        widget->priv->label = gtk_label_new ("");
+
+        gtk_widget_show (widget->priv->label);
+        gtk_box_pack_start (GTK_BOX (box), widget->priv->label, FALSE, FALSE, 0);
+
+        update_time_format (widget);
+        update_timeout_cb (widget);
+}
+
+static void
+gdm_clock_widget_finalize (GObject *object)
+{
+        GdmClockWidget *clock_widget;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_CLOCK_WIDGET (object));
+
+        clock_widget = GDM_CLOCK_WIDGET (object);
+
+        g_return_if_fail (clock_widget->priv != NULL);
+
+        remove_timeout (clock_widget);
+
+        G_OBJECT_CLASS (gdm_clock_widget_parent_class)->finalize (object);
+}
+
+GtkWidget *
+gdm_clock_widget_new (void)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_CLOCK_WIDGET,
+                               NULL);
+
+        return GTK_WIDGET (object);
+}
diff --git a/gui/simple-greeter/gdm-clock-widget.h b/gui/simple-greeter/gdm-clock-widget.h
new file mode 100644
index 0000000..065114e
--- /dev/null
+++ b/gui/simple-greeter/gdm-clock-widget.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ * Copyright (C) 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *  Written by: Ray Strode <rstrode redhat com>
+ *              Jon McCann <mccann jhu edu>
+ */
+
+#ifndef __GDM_CLOCK_WIDGET_H
+#define __GDM_CLOCK_WIDGET_H
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_CLOCK_WIDGET         (gdm_clock_widget_get_type ())
+#define GDM_CLOCK_WIDGET(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_CLOCK_WIDGET, 
GdmClockWidget))
+#define GDM_CLOCK_WIDGET_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_CLOCK_WIDGET, 
GdmClockWidgetClass))
+#define GDM_IS_CLOCK_WIDGET(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_CLOCK_WIDGET))
+#define GDM_IS_CLOCK_WIDGET_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_CLOCK_WIDGET))
+#define GDM_CLOCK_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_CLOCK_WIDGET, 
GdmClockWidgetClass))
+
+typedef struct GdmClockWidgetPrivate GdmClockWidgetPrivate;
+
+typedef struct
+{
+        GtkAlignment           parent;
+        GdmClockWidgetPrivate *priv;
+} GdmClockWidget;
+
+typedef struct
+{
+        GtkAlignmentClass              parent_class;
+} GdmClockWidgetClass;
+
+GType                  gdm_clock_widget_get_type               (void);
+GtkWidget *            gdm_clock_widget_new                    (void);
+
+#endif /* __GDM_CLOCK_WIDGET_H */
diff --git a/gui/simple-greeter/gdm-extension-list.c b/gui/simple-greeter/gdm-extension-list.c
new file mode 100644
index 0000000..a0be786
--- /dev/null
+++ b/gui/simple-greeter/gdm-extension-list.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *  Written by: Ray Strode <rstrode redhat com>
+ */
+
+#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 "gdm-extension-list.h"
+
+#define GDM_EXTENSION_LIST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_EXTENSION_LIST, 
GdmExtensionListPrivate))
+typedef gboolean (* GdmExtensionListForeachFunc) (GdmExtensionList    *extension_list,
+                                                  GdmLoginExtension *extension,
+                                                  gpointer             data);
+
+
+struct GdmExtensionListPrivate
+{
+        GtkWidget *box;
+        GList     *extensions;
+};
+
+enum {
+        ACTIVATED = 0,
+        DEACTIVATED,
+        NUMBER_OF_SIGNALS
+};
+
+static guint    signals[NUMBER_OF_SIGNALS];
+
+static void     gdm_extension_list_class_init  (GdmExtensionListClass *klass);
+static void     gdm_extension_list_init        (GdmExtensionList      *extension_list);
+static void     gdm_extension_list_finalize    (GObject          *object);
+
+G_DEFINE_TYPE (GdmExtensionList, gdm_extension_list, GTK_TYPE_ALIGNMENT);
+
+static void
+on_extension_toggled (GdmExtensionList *widget,
+                      GtkRadioButton   *button)
+{
+        GdmLoginExtension *extension;
+
+        extension = g_object_get_data (G_OBJECT (button), "gdm-extension");
+
+        if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) {
+
+                GList     *extension_node;
+                /* Sort the list such that the extensions the user clicks last end
+                 * up first.  This doesn't change the order in which the extensions
+                 * appear in the UI, but will affect which extensions we implicitly
+                 * activate if the currently active extension gets disabled.
+                 */
+                extension_node = g_list_find (widget->priv->extensions, extension);
+                if (extension_node != NULL) {
+                        widget->priv->extensions = g_list_delete_link (widget->priv->extensions, 
extension_node);
+                        widget->priv->extensions = g_list_prepend (widget->priv->extensions,
+                                                              extension);
+                }
+
+                g_signal_emit (widget, signals[ACTIVATED], 0, extension);
+        } else {
+                g_signal_emit (widget, signals[DEACTIVATED], 0, extension);
+        }
+}
+
+static GdmLoginExtension *
+gdm_extension_list_foreach_extension (GdmExtensionList           *extension_list,
+                                      GdmExtensionListForeachFunc search_func,
+                                      gpointer                           data)
+{
+        GList *node;
+
+        for (node = extension_list->priv->extensions; node != NULL; node = node->next) {
+                GdmLoginExtension *extension;
+
+                extension = node->data;
+
+                if (search_func (extension_list, extension, data)) {
+                        return g_object_ref (extension);
+                }
+        }
+
+        return NULL;
+}
+
+static void
+on_extension_enabled (GdmExtensionList *extension_list,
+                      GdmLoginExtension     *extension)
+{
+        GtkWidget *button;
+
+        button = g_object_get_data (G_OBJECT (extension), "gdm-extension-list-button");
+
+        gtk_widget_set_sensitive (button, TRUE);
+}
+
+static gboolean
+gdm_extension_list_set_active_extension (GdmExtensionList    *widget,
+                                         GdmLoginExtension *extension)
+{
+        GtkWidget *button;
+        gboolean   was_sensitive;
+        gboolean   was_activated;
+
+        if (!gdm_login_extension_is_visible (extension)) {
+                return FALSE;
+        }
+
+        was_sensitive = gtk_widget_get_sensitive (GTK_WIDGET (widget));
+        gtk_widget_set_sensitive (GTK_WIDGET (widget), TRUE);
+
+        button = GTK_WIDGET (g_object_get_data (G_OBJECT (extension),
+                             "gdm-extension-list-button"));
+
+        was_activated = FALSE;
+        if (gtk_widget_is_sensitive (button)) {
+                if (gtk_widget_activate (button)) {
+                        was_activated = TRUE;
+                }
+        }
+
+        gtk_widget_set_sensitive (GTK_WIDGET (widget), was_sensitive);
+        return was_activated;
+}
+
+static void
+activate_first_available_extension (GdmExtensionList *extension_list)
+{
+        GList *node;
+
+        node = extension_list->priv->extensions;
+        while (node != NULL) {
+                GdmLoginExtension   *extension;
+
+                extension = GDM_LOGIN_EXTENSION (node->data);
+
+                if (gdm_extension_list_set_active_extension (extension_list, extension)) {
+                        break;
+                }
+
+                node = node->next;
+        }
+}
+
+static void
+on_extension_disabled (GdmExtensionList  *extension_list,
+                       GdmLoginExtension *extension)
+{
+        GtkWidget *button;
+        gboolean   was_active;
+
+        button = g_object_get_data (G_OBJECT (extension), "gdm-extension-list-button");
+        was_active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+
+        gtk_widget_set_sensitive (button, FALSE);
+
+        if (was_active) {
+                activate_first_available_extension (extension_list);
+        }
+}
+
+void
+gdm_extension_list_add_extension (GdmExtensionList  *extension_list,
+                                  GdmLoginExtension *extension)
+{
+        GtkWidget *image;
+        GtkWidget *button;
+        GIcon     *icon;
+        char      *description;
+
+        if (extension_list->priv->extensions == NULL) {
+                button = gtk_radio_button_new (NULL);
+        } else {
+                GdmLoginExtension *previous_extension;
+                GtkRadioButton *previous_button;
+
+                previous_extension = GDM_LOGIN_EXTENSION (extension_list->priv->extensions->data);
+                previous_button = GTK_RADIO_BUTTON (g_object_get_data (G_OBJECT (previous_extension), 
"gdm-extension-list-button"));
+                button = gtk_radio_button_new_from_widget (previous_button);
+        }
+        g_object_set_data (G_OBJECT (extension), "gdm-extension-list-button", button);
+
+        g_object_set (G_OBJECT (button), "draw-indicator", FALSE, NULL);
+        g_object_set_data (G_OBJECT (button), "gdm-extension", extension);
+        g_signal_connect_swapped (button, "toggled",
+                                  G_CALLBACK (on_extension_toggled),
+                                  extension_list);
+
+        gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
+        gtk_widget_set_sensitive (button, gdm_login_extension_is_enabled (extension));
+
+        g_signal_connect_swapped (G_OBJECT (extension), "enabled",
+                                  G_CALLBACK (on_extension_enabled),
+                                  extension_list);
+
+        g_signal_connect_swapped (G_OBJECT (extension), "disabled",
+                                  G_CALLBACK (on_extension_disabled),
+                                  extension_list);
+
+        icon = gdm_login_extension_get_icon (extension);
+        image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_SMALL_TOOLBAR);
+        g_object_unref (icon);
+
+        gtk_widget_show (image);
+        gtk_container_add (GTK_CONTAINER (button), image);
+        description = gdm_login_extension_get_description (extension);
+        gtk_widget_set_tooltip_text (button, description);
+        g_free (description);
+        gtk_widget_show (button);
+
+        gtk_container_add (GTK_CONTAINER (extension_list->priv->box), button);
+        extension_list->priv->extensions = g_list_append (extension_list->priv->extensions,
+                                                g_object_ref (extension));
+
+        if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) {
+                g_signal_emit (extension_list, signals[ACTIVATED], 0, extension);
+        }
+}
+
+void
+gdm_extension_list_remove_extension (GdmExtensionList  *extension_list,
+                                     GdmLoginExtension *extension)
+{
+        GtkWidget *button;
+        GList     *node;
+
+        node = g_list_find (extension_list->priv->extensions, extension);
+
+        if (node == NULL) {
+                return;
+        }
+
+        extension_list->priv->extensions = g_list_delete_link (extension_list->priv->extensions, node);
+
+        button = g_object_get_data (G_OBJECT (extension), "gdm-extension-list-button");
+
+        if (button != NULL) {
+            g_signal_handlers_disconnect_by_func (G_OBJECT (extension),
+                                                  G_CALLBACK (on_extension_enabled),
+                                                  extension_list);
+            g_signal_handlers_disconnect_by_func (G_OBJECT (extension),
+                                                  G_CALLBACK (on_extension_disabled),
+                                                  extension_list);
+            gtk_widget_destroy (button);
+            g_object_set_data (G_OBJECT (extension), "gdm-extension-list-button", NULL);
+        }
+
+        g_object_unref (extension);
+
+        activate_first_available_extension (extension_list);
+}
+
+static void
+gdm_extension_list_class_init (GdmExtensionListClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gdm_extension_list_finalize;
+
+        signals [ACTIVATED] = g_signal_new ("activated",
+                                            G_TYPE_FROM_CLASS (object_class),
+                                            G_SIGNAL_RUN_FIRST,
+                                            G_STRUCT_OFFSET (GdmExtensionListClass, activated),
+                                            NULL,
+                                            NULL,
+                                            g_cclosure_marshal_VOID__OBJECT,
+                                            G_TYPE_NONE,
+                                            1, G_TYPE_OBJECT);
+
+        signals [DEACTIVATED] = g_signal_new ("deactivated",
+                                            G_TYPE_FROM_CLASS (object_class),
+                                            G_SIGNAL_RUN_FIRST,
+                                            G_STRUCT_OFFSET (GdmExtensionListClass, deactivated),
+                                            NULL,
+                                            NULL,
+                                            g_cclosure_marshal_VOID__OBJECT,
+                                            G_TYPE_NONE,
+                                            1, G_TYPE_OBJECT);
+
+        g_type_class_add_private (klass, sizeof (GdmExtensionListPrivate));
+}
+
+static void
+gdm_extension_list_init (GdmExtensionList *widget)
+{
+        widget->priv = GDM_EXTENSION_LIST_GET_PRIVATE (widget);
+
+        gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 0, 0, 0);
+        gtk_alignment_set (GTK_ALIGNMENT (widget), 0.0, 0.0, 0, 0);
+
+        widget->priv->box = gtk_hbox_new (TRUE, 2);
+        gtk_widget_show (widget->priv->box);
+        gtk_container_add (GTK_CONTAINER (widget),
+                           widget->priv->box);
+}
+
+static void
+gdm_extension_list_finalize (GObject *object)
+{
+        GdmExtensionList *widget;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_EXTENSION_LIST (object));
+
+        widget = GDM_EXTENSION_LIST (object);
+
+        g_list_foreach (widget->priv->extensions, (GFunc) g_object_unref, NULL);
+        g_list_free (widget->priv->extensions);
+
+        G_OBJECT_CLASS (gdm_extension_list_parent_class)->finalize (object);
+}
+
+GtkWidget *
+gdm_extension_list_new (void)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_EXTENSION_LIST, NULL);
+
+        return GTK_WIDGET (object);
+}
+
+static gboolean
+gdm_extension_list_extension_is_active (GdmExtensionList  *extension_list,
+                                        GdmLoginExtension *extension)
+{
+        GtkWidget *button;
+
+        button = g_object_get_data (G_OBJECT (extension), "gdm-extension-list-button");
+
+        return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+}
+
+GdmLoginExtension *
+gdm_extension_list_get_active_extension (GdmExtensionList *widget)
+{
+        return gdm_extension_list_foreach_extension (widget,
+                                                     (GdmExtensionListForeachFunc)
+                                                     gdm_extension_list_extension_is_active,
+                                                     NULL);
+}
+
+int
+gdm_extension_list_get_number_of_visible_extensions (GdmExtensionList *widget)
+{
+        GList *node;
+        int number_of_visible_extensions;
+
+        number_of_visible_extensions = 0;
+        for (node = widget->priv->extensions; node != NULL; node = node->next) {
+                GdmLoginExtension *extension;
+
+                extension = node->data;
+
+                if (gdm_login_extension_is_enabled (extension) && gdm_login_extension_is_visible 
(extension)) {
+                        number_of_visible_extensions++;
+                }
+        }
+
+        return number_of_visible_extensions;
+}
diff --git a/gui/simple-greeter/gdm-extension-list.h b/gui/simple-greeter/gdm-extension-list.h
new file mode 100644
index 0000000..0c9964e
--- /dev/null
+++ b/gui/simple-greeter/gdm-extension-list.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *  Written by: Ray Strode <rstrode redhat com>
+ */
+
+#ifndef __GDM_EXTENSION_LIST_H
+#define __GDM_EXTENSION_LIST_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include "gdm-login-extension.h"
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_EXTENSION_LIST         (gdm_extension_list_get_type ())
+#define GDM_EXTENSION_LIST(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_EXTENSION_LIST, 
GdmExtensionList))
+#define GDM_EXTENSION_LIST_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_EXTENSION_LIST, 
GdmExtensionListClass))
+#define GDM_IS_EXTENSION_LIST(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_EXTENSION_LIST))
+#define GDM_IS_EXTENSION_LIST_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_EXTENSION_LIST))
+#define GDM_EXTENSION_LIST_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_EXTENSION_LIST, 
GdmExtensionListClass))
+
+typedef struct GdmExtensionListPrivate GdmExtensionListPrivate;
+typedef struct _GdmExtensionList GdmExtensionList;
+
+struct _GdmExtensionList
+{
+        GtkAlignment        parent;
+        GdmExtensionListPrivate *priv;
+};
+
+typedef struct
+{
+        GtkAlignmentClass       parent_class;
+
+        void (* deactivated)    (GdmExtensionList    *widget,
+                                 GdmLoginExtension *extension);
+        void (* activated)      (GdmExtensionList    *widget,
+                                 GdmLoginExtension *extension);
+} GdmExtensionListClass;
+
+GType       gdm_extension_list_get_type               (void);
+GtkWidget * gdm_extension_list_new                    (void);
+
+GdmLoginExtension *gdm_extension_list_get_active_extension (GdmExtensionList    *widget);
+void               gdm_extension_list_add_extension        (GdmExtensionList    *widget,
+                                                            GdmLoginExtension *extension);
+void               gdm_extension_list_remove_extension     (GdmExtensionList    *widget,
+                                                            GdmLoginExtension *extension);
+
+int         gdm_extension_list_get_number_of_visible_extensions (GdmExtensionList *widget);
+G_END_DECLS
+
+#endif /* __GDM_EXTENSION_LIST_H */
diff --git a/gui/simple-greeter/gdm-greeter-login-window.c b/gui/simple-greeter/gdm-greeter-login-window.c
new file mode 100644
index 0000000..529c418
--- /dev/null
+++ b/gui/simple-greeter/gdm-greeter-login-window.c
@@ -0,0 +1,2623 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Written by: William Jon McCann <mccann jhu edu>
+ *             Ray Strode <rstrode redhat com>
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <pwd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+#include <X11/XKBlib.h>
+
+#include <gtk/gtk.h>
+
+#include "gdm-settings-client.h"
+#include "gdm-settings-keys.h"
+#include "gdm-profile.h"
+
+#include "gdm-client.h"
+#include "gdm-greeter-login-window.h"
+#include "gdm-user-chooser-widget.h"
+#include "gdm-session-option-widget.h"
+#include "gdm-extension-list.h"
+
+#include "extensions/unified/gdm-unified-extension.h"
+
+#ifdef HAVE_PAM
+#include <security/pam_appl.h>
+#define PW_ENTRY_SIZE PAM_MAX_RESP_SIZE
+#else
+#define PW_ENTRY_SIZE GDM_MAX_PASS
+#endif
+
+#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"
+
+#define UI_XML_FILE       "gdm-greeter-login-window.ui"
+
+#define LOGIN_SCREEN_SCHEMA         "org.gnome.login-screen"
+#define KEY_BANNER_MESSAGE_ENABLED  "banner-message-enable"
+#define KEY_BANNER_MESSAGE_TEXT     "banner-message-text"
+#define KEY_LOGO                    "fallback-logo"
+#define KEY_DISABLE_USER_LIST       "disable-user-list"
+
+#define LSB_RELEASE_COMMAND "lsb_release -d"
+
+#define GDM_GREETER_LOGIN_WINDOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), 
GDM_TYPE_GREETER_LOGIN_WINDOW, GdmGreeterLoginWindowPrivate))
+#define GDM_CUSTOM_SESSION "custom"
+
+#define INFO_MESSAGE_DURATION 2
+#define PROBLEM_MESSAGE_DURATION 3
+
+enum {
+        MODE_UNDEFINED = 0,
+        MODE_TIMED_LOGIN,
+        MODE_SELECTION,
+        MODE_AUTHENTICATION,
+        MODE_MULTIPLE_AUTHENTICATION,
+};
+
+enum {
+        LOGIN_BUTTON_HIDDEN = 0,
+        LOGIN_BUTTON_ANSWER_QUERY,
+        LOGIN_BUTTON_TIMED_LOGIN
+};
+
+struct GdmGreeterLoginWindowPrivate
+{
+        GtkBuilder      *builder;
+        GtkWidget       *session_option_widget;
+        GtkWidget       *user_chooser;
+        GtkWidget       *extension_list;
+        GtkWidget       *auth_banner_label;
+        GtkWidget       *current_button;
+        GtkWidget       *auth_page_box;
+        guint            display_is_local : 1;
+        guint            user_chooser_loaded : 1;
+        GSettings       *settings;
+        GList           *extensions;
+        GdmLoginExtension *active_extension;
+        GList           *extensions_to_enable;
+        GList           *extensions_to_stop;
+
+        gboolean         banner_message_enabled;
+        gulong           gsettings_cnxn;
+
+        guint            last_mode;
+        guint            dialog_mode;
+        guint            next_mode;
+
+        gboolean         user_list_disabled;
+        guint            num_queries;
+
+        gboolean         timed_login_already_enabled;
+        gboolean         timed_login_enabled;
+        guint            timed_login_delay;
+        char            *timed_login_username;
+        guint            timed_login_timeout_id;
+
+        guint            login_button_handler_id;
+        guint            start_session_handler_id;
+
+        char            *service_name_of_session_ready_to_start;
+};
+
+enum {
+        PROP_0,
+        PROP_DISPLAY_IS_LOCAL,
+};
+
+enum {
+        START_CONVERSATION,
+        BEGIN_AUTO_LOGIN,
+        BEGIN_VERIFICATION,
+        BEGIN_VERIFICATION_FOR_USER,
+        QUERY_ANSWER,
+        START_SESSION,
+        USER_SELECTED,
+        SESSION_SELECTED,
+        CANCELLED,
+        LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void     gdm_greeter_login_window_class_init   (GdmGreeterLoginWindowClass *klass);
+static void     gdm_greeter_login_window_init         (GdmGreeterLoginWindow      *greeter_login_window);
+static void     gdm_greeter_login_window_finalize     (GObject                    *object);
+
+static void     restart_timed_login_timeout (GdmGreeterLoginWindow *login_window);
+static void     on_user_unchosen            (GdmUserChooserWidget *user_chooser,
+                                             GdmGreeterLoginWindow *login_window);
+
+static void     switch_mode                 (GdmGreeterLoginWindow *login_window,
+                                             int                    number);
+static void     update_banner_message       (GdmGreeterLoginWindow *login_window);
+static void     reset_dialog                (GdmGreeterLoginWindow *login_window,
+                                             guint                  dialog_mode);
+static void     gdm_greeter_login_window_start_session_when_ready (GdmGreeterLoginWindow *login_window,
+                                                                   const char            *service_name);
+static void     handle_stopped_conversation (GdmGreeterLoginWindow *login_window,
+                                             const char            *service_name);
+
+static void     begin_single_service_verification (GdmGreeterLoginWindow *login_window,
+                                                   const char            *service_name);
+
+G_DEFINE_TYPE (GdmGreeterLoginWindow, gdm_greeter_login_window, GTK_TYPE_WINDOW)
+
+static void
+set_busy (GdmGreeterLoginWindow *login_window)
+{
+        GdkCursor *cursor;
+
+        cursor = gdk_cursor_new (GDK_WATCH);
+        gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (login_window)), cursor);
+        g_object_unref (cursor);
+}
+
+static void
+set_ready (GdmGreeterLoginWindow *login_window)
+{
+        gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (login_window)), NULL);
+}
+
+static void
+set_sensitive (GdmGreeterLoginWindow *login_window,
+               gboolean               sensitive)
+{
+        GtkWidget *box;
+
+        box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "buttonbox"));
+        gtk_widget_set_sensitive (box, sensitive);
+
+        gtk_widget_set_sensitive (login_window->priv->user_chooser, sensitive);
+}
+
+static void
+set_focus (GdmGreeterLoginWindow *login_window)
+{
+        gdk_window_focus (gtk_widget_get_window (GTK_WIDGET (login_window)), GDK_CURRENT_TIME);
+
+        if (login_window->priv->active_extension != NULL &&
+            gdm_login_extension_focus (login_window->priv->active_extension)) {
+                char *name;
+                name = gdm_login_extension_get_name (login_window->priv->active_extension);
+                g_debug ("GdmGreeterLoginWindow: focusing extension %s", name);
+                g_free (name);
+        } else if (gtk_widget_get_realized (login_window->priv->user_chooser) && ! gtk_widget_has_focus 
(login_window->priv->user_chooser)) {
+                gtk_widget_grab_focus (login_window->priv->user_chooser);
+        }
+
+}
+
+static gboolean
+queue_message_for_extension (GdmLoginExtension *extension,
+                             const char          *message)
+{
+        gdm_login_extension_queue_message (extension,
+                                             GDM_SERVICE_MESSAGE_TYPE_INFO,
+                                             message);
+        return FALSE;
+}
+
+static void
+set_message (GdmGreeterLoginWindow *login_window,
+             const char            *text)
+{
+        g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window));
+
+        g_list_foreach (login_window->priv->extensions,
+                        (GFunc) queue_message_for_extension,
+                        (gpointer) text);
+}
+
+static void
+on_user_interaction (GdmGreeterLoginWindow *login_window)
+{
+        g_debug ("GdmGreeterLoginWindow: user is interacting with session!\n");
+        restart_timed_login_timeout (login_window);
+}
+
+static GdkFilterReturn
+on_xevent (XEvent                *xevent,
+           GdkEvent              *event,
+           GdmGreeterLoginWindow *login_window)
+{
+        switch (xevent->xany.type) {
+                case KeyPress:
+                case KeyRelease:
+                case ButtonPress:
+                case ButtonRelease:
+                        on_user_interaction (login_window);
+                        break;
+                case  PropertyNotify:
+                        if (xevent->xproperty.atom == gdk_x11_get_xatom_by_name ("_NET_WM_USER_TIME")) {
+                                on_user_interaction (login_window);
+                        }
+                        break;
+
+                default:
+                        break;
+        }
+
+        return GDK_FILTER_CONTINUE;
+}
+
+static void
+stop_watching_for_user_interaction (GdmGreeterLoginWindow *login_window)
+{
+        gdk_window_remove_filter (NULL,
+                                  (GdkFilterFunc) on_xevent,
+                                  login_window);
+}
+
+static void
+remove_timed_login_timeout (GdmGreeterLoginWindow *login_window)
+{
+        if (login_window->priv->timed_login_timeout_id > 0) {
+                g_debug ("GdmGreeterLoginWindow: removing timed login timer");
+                g_source_remove (login_window->priv->timed_login_timeout_id);
+                login_window->priv->timed_login_timeout_id = 0;
+        }
+
+        stop_watching_for_user_interaction (login_window);
+}
+
+static gboolean
+timed_login_timer (GdmGreeterLoginWindow *login_window)
+{
+        set_sensitive (login_window, FALSE);
+        set_message (login_window, _("Automatically logging in…"));
+
+        g_debug ("GdmGreeterLoginWindow: timer expired");
+        login_window->priv->timed_login_timeout_id = 0;
+
+        return FALSE;
+}
+
+static void
+watch_for_user_interaction (GdmGreeterLoginWindow *login_window)
+{
+        gdk_window_add_filter (NULL,
+                               (GdkFilterFunc) on_xevent,
+                               login_window);
+}
+
+static void
+restart_timed_login_timeout (GdmGreeterLoginWindow *login_window)
+{
+        remove_timed_login_timeout (login_window);
+
+        if (login_window->priv->timed_login_enabled) {
+                g_debug ("GdmGreeterLoginWindow: adding timed login timer");
+                watch_for_user_interaction (login_window);
+                login_window->priv->timed_login_timeout_id = g_timeout_add_seconds 
(login_window->priv->timed_login_delay,
+                                                                                    
(GSourceFunc)timed_login_timer,
+                                                                                    login_window);
+
+                gdm_chooser_widget_set_item_timer (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser),
+                                                   GDM_USER_CHOOSER_USER_AUTO,
+                                                   login_window->priv->timed_login_delay * 1000);
+        }
+}
+
+static void
+show_widget (GdmGreeterLoginWindow *login_window,
+             const char            *name,
+             gboolean               visible)
+{
+        GtkWidget *widget;
+
+        widget = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, name));
+        if (widget != NULL) {
+                if (visible) {
+                        gtk_widget_show (widget);
+                } else {
+                        gtk_widget_hide (widget);
+                }
+        }
+}
+
+static void
+hide_extension_actions (GdmLoginExtension *extension)
+{
+        GtkActionGroup *actions;
+
+        actions = gdm_login_extension_get_actions (extension);
+
+        if (actions != NULL) {
+                gtk_action_group_set_visible (actions, FALSE);
+                gtk_action_group_set_sensitive (actions, FALSE);
+                g_object_unref (actions);
+        }
+}
+
+static void
+grab_default_button_for_extension (GdmLoginExtension *extension)
+{
+        GtkActionGroup *actions;
+        GtkAction *action;
+        GSList    *proxies, *node;
+
+        actions = gdm_login_extension_get_actions (extension);
+
+        if (actions == NULL) {
+                return;
+        }
+
+        action = gtk_action_group_get_action (actions, GDM_LOGIN_EXTENSION_DEFAULT_ACTION);
+        g_object_unref (actions);
+
+        if (action == NULL) {
+                return;
+        }
+
+        proxies = gtk_action_get_proxies (action);
+        for (node = proxies; node != NULL; node = node->next) {
+                GtkWidget *widget;
+
+                widget = GTK_WIDGET (node->data);
+
+                if (gtk_widget_get_can_default (widget) &&
+                    gtk_widget_get_visible (widget)) {
+                        gtk_widget_grab_default (widget);
+                        break;
+                }
+        }
+}
+
+static void
+show_extension_actions (GdmLoginExtension *extension)
+{
+        GtkActionGroup *actions;
+
+        actions = gdm_login_extension_get_actions (extension);
+        if (actions != NULL) {
+                gtk_action_group_set_sensitive (actions, TRUE);
+                gtk_action_group_set_visible (actions, TRUE);
+                g_object_unref (actions);
+        }
+}
+
+static void
+on_login_button_clicked_timed_login (GtkButton             *button,
+                                     GdmGreeterLoginWindow *login_window)
+{
+        set_busy (login_window);
+        set_sensitive (login_window, FALSE);
+}
+
+static void
+set_log_in_button_mode (GdmGreeterLoginWindow *login_window,
+                        int                    mode)
+{
+        GtkWidget *button;
+        GtkWidget *login_button;
+        GtkWidget *unlock_button;
+        char      *item;
+        gboolean   in_use;
+
+        in_use = FALSE;
+        item = gdm_chooser_widget_get_active_item (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser));
+        if (item != NULL) {
+                gboolean res;
+
+                res = gdm_chooser_widget_lookup_item (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser),
+                                                      item,
+                                                      NULL, /* image */
+                                                      NULL, /* name */
+                                                      NULL, /* comment */
+                                                      NULL, /* priority */
+                                                      &in_use,
+                                                      NULL); /* is separate */
+
+                if (!res) {
+                        in_use = FALSE;
+                }
+        }
+
+        if (login_window->priv->current_button != NULL) {
+                /* disconnect any signals */
+                if (login_window->priv->login_button_handler_id > 0) {
+                        g_signal_handler_disconnect (login_window->priv->current_button,
+                                                     login_window->priv->login_button_handler_id);
+                        login_window->priv->login_button_handler_id = 0;
+                }
+        }
+
+        unlock_button = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "unlock-button"));
+        login_button = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "log-in-button"));
+
+        if (in_use) {
+                gtk_widget_hide (login_button);
+                button = unlock_button;
+        } else {
+                gtk_widget_hide (unlock_button);
+                button = login_button;
+        }
+        gtk_widget_grab_default (button);
+
+        login_window->priv->current_button = button;
+
+        g_list_foreach (login_window->priv->extensions, (GFunc) hide_extension_actions, NULL);
+
+        switch (mode) {
+        case LOGIN_BUTTON_HIDDEN:
+                if (login_window->priv->active_extension != NULL) {
+                        hide_extension_actions (login_window->priv->active_extension);
+                }
+
+                gtk_widget_hide (button);
+                break;
+        case LOGIN_BUTTON_ANSWER_QUERY:
+                if (login_window->priv->active_extension != NULL) {
+                        show_extension_actions (login_window->priv->active_extension);
+                        grab_default_button_for_extension (login_window->priv->active_extension);
+                }
+
+                gtk_widget_hide (button);
+                break;
+        case LOGIN_BUTTON_TIMED_LOGIN:
+                login_window->priv->login_button_handler_id = g_signal_connect (button, "clicked", 
G_CALLBACK (on_login_button_clicked_timed_login), login_window);
+                gtk_widget_show (button);
+                break;
+        default:
+                g_assert_not_reached ();
+                break;
+        }
+}
+
+static gboolean
+user_chooser_has_no_user (GdmGreeterLoginWindow *login_window)
+{
+        guint num_items;
+
+        num_items = gdm_chooser_widget_get_number_of_items (GDM_CHOOSER_WIDGET 
(login_window->priv->user_chooser));
+        g_debug ("GdmGreeterLoginWindow: loaded=%d num_items=%d",
+                 login_window->priv->user_chooser_loaded,
+                 num_items);
+        return (login_window->priv->user_chooser_loaded && num_items == 0);
+}
+
+static void
+maybe_show_cancel_button (GdmGreeterLoginWindow *login_window)
+{
+        gboolean show;
+
+        show = FALSE;
+
+        /* only show the cancel button if there is something to go
+           back to */
+
+        switch (login_window->priv->dialog_mode) {
+        case MODE_SELECTION:
+                /* should never have anything to return to from here */
+                show = FALSE;
+                break;
+        case MODE_TIMED_LOGIN:
+                /* should always have something to return to from here */
+                show = TRUE;
+                break;
+        case MODE_AUTHENTICATION:
+        case MODE_MULTIPLE_AUTHENTICATION:
+                if (login_window->priv->num_queries > 1) {
+                        /* if we are inside a pam conversation past
+                           the first step */
+                        show = TRUE;
+                } else {
+                        if (login_window->priv->user_list_disabled || user_chooser_has_no_user 
(login_window)) {
+                                show = FALSE;
+                        } else {
+                                show = TRUE;
+                        }
+                }
+                break;
+        default:
+                g_assert_not_reached ();
+        }
+
+        show_widget (login_window, "cancel-button", show);
+}
+
+static void
+update_extension_list_visibility (GdmGreeterLoginWindow *login_window)
+{
+        int number_of_extensions;
+
+        if (login_window->priv->dialog_mode != MODE_MULTIPLE_AUTHENTICATION) {
+                gtk_widget_hide (login_window->priv->extension_list);
+                return;
+        }
+
+        number_of_extensions = gdm_extension_list_get_number_of_visible_extensions (GDM_EXTENSION_LIST 
(login_window->priv->extension_list));
+        if (number_of_extensions > 1) {
+                gtk_widget_show (login_window->priv->extension_list);
+        } else {
+                gtk_widget_hide (login_window->priv->extension_list);
+        }
+}
+
+static void
+switch_mode (GdmGreeterLoginWindow *login_window,
+             int                    number)
+{
+        GtkWidget  *box;
+
+        /* Should never switch to MODE_UNDEFINED */
+        g_assert (number != MODE_UNDEFINED);
+
+        /* we want to run this even if we're supposed to
+           be in the mode already so that we reset everything
+           to a known state */
+        if (login_window->priv->dialog_mode != number) {
+                login_window->priv->last_mode = login_window->priv->dialog_mode;
+                login_window->priv->dialog_mode = number;
+        }
+
+        login_window->priv->next_mode = MODE_UNDEFINED;
+
+        switch (number) {
+        case MODE_SELECTION:
+                set_log_in_button_mode (login_window, LOGIN_BUTTON_HIDDEN);
+                set_sensitive (login_window, TRUE);
+                gtk_widget_hide (login_window->priv->session_option_widget);
+                break;
+        case MODE_TIMED_LOGIN:
+                set_log_in_button_mode (login_window, LOGIN_BUTTON_TIMED_LOGIN);
+                set_sensitive (login_window, TRUE);
+                gtk_widget_show (login_window->priv->session_option_widget);
+                break;
+        case MODE_AUTHENTICATION:
+        case MODE_MULTIPLE_AUTHENTICATION:
+                set_log_in_button_mode (login_window, LOGIN_BUTTON_ANSWER_QUERY);
+                set_sensitive (login_window, FALSE);
+                gtk_widget_show (login_window->priv->session_option_widget);
+                break;
+        default:
+                g_assert_not_reached ();
+        }
+
+        show_widget (login_window, "auth-input-box", FALSE);
+        update_extension_list_visibility (login_window);
+        maybe_show_cancel_button (login_window);
+
+        /*
+         * The rest of this function sets up the user list, so just return if
+         * the user list is disabled.
+         */
+        if (login_window->priv->user_list_disabled && number != MODE_TIMED_LOGIN) {
+                return;
+        }
+
+        box = gtk_widget_get_parent (login_window->priv->user_chooser);
+        if (GTK_IS_BOX (box)) {
+                guint       padding;
+                GtkPackType pack_type;
+
+                gtk_box_query_child_packing (GTK_BOX (box),
+                                             login_window->priv->user_chooser,
+                                             NULL,
+                                             NULL,
+                                             &padding,
+                                             &pack_type);
+                gtk_box_set_child_packing (GTK_BOX (box),
+                                           login_window->priv->user_chooser,
+                                           number == MODE_SELECTION,
+                                           number == MODE_SELECTION,
+                                           padding,
+                                           pack_type);
+        }
+}
+
+static GdmLoginExtension *
+find_extension_with_service_name (GdmGreeterLoginWindow *login_window,
+                                  const char            *service_name)
+{
+        GList *node;
+
+        node = login_window->priv->extensions;
+        while (node != NULL) {
+                GdmLoginExtension *extension;
+                char *extension_service_name;
+                gboolean has_service_name;
+
+                extension = GDM_LOGIN_EXTENSION (node->data);
+
+                extension_service_name = gdm_login_extension_get_service_name (extension);
+                has_service_name = strcmp (service_name, extension_service_name) == 0;
+                g_free (extension_service_name);
+
+                if (has_service_name) {
+                        return extension;
+                }
+
+                node = node->next;
+        }
+
+        return NULL;
+}
+
+static gboolean
+reset_extension (GdmLoginExtension   *extension,
+                 GdmGreeterLoginWindow *login_window)
+{
+        char *name;
+
+        name = gdm_login_extension_get_name (extension);
+        g_debug ("Resetting extension '%s'", name);
+        g_free (name);
+
+        login_window->priv->extensions_to_enable = g_list_remove (login_window->priv->extensions_to_enable, 
extension);
+
+        hide_extension_actions (extension);
+        gdm_extension_list_remove_extension (GDM_EXTENSION_LIST (login_window->priv->extension_list), 
extension);
+        gdm_login_extension_reset (extension);
+        return FALSE;
+}
+
+static gboolean
+extensions_are_enabled (GdmGreeterLoginWindow *login_window)
+{
+
+        GList *node;
+
+        node = login_window->priv->extensions;
+        while (node != NULL) {
+                GdmLoginExtension *extension;
+
+                extension = GDM_LOGIN_EXTENSION (node->data);
+
+                if (!gdm_login_extension_is_enabled (extension)) {
+                        return FALSE;
+                }
+
+                node = node->next;
+        }
+
+        return TRUE;
+}
+
+static gboolean
+can_jump_to_authenticate (GdmGreeterLoginWindow *login_window)
+{
+        gboolean res;
+
+        if (!login_window->priv->user_chooser_loaded) {
+                res = FALSE;
+        } else if (!extensions_are_enabled (login_window)) {
+                res = FALSE;
+        } else if (login_window->priv->dialog_mode == MODE_AUTHENTICATION) {
+                res = FALSE;
+        } else if (login_window->priv->dialog_mode == MODE_MULTIPLE_AUTHENTICATION) {
+                res = FALSE;
+        } else if (login_window->priv->user_list_disabled) {
+                res = (login_window->priv->timed_login_username == NULL);
+        } else {
+                res = user_chooser_has_no_user (login_window);
+        }
+
+        return res;
+}
+
+static void
+begin_other_verification (GdmGreeterLoginWindow *login_window)
+{
+        /* FIXME: we should drop this code and do all OTHER handling
+         * entirely from within the extension
+         * (ala how smart card manages its "Smartcard Authentication" item)
+         */
+        if (find_extension_with_service_name (login_window, "gdm-password") != NULL) {
+                begin_single_service_verification (login_window, "gdm-password");
+        } else {
+                begin_single_service_verification (login_window, "gdm");
+        }
+}
+
+static void
+set_extension_active (GdmGreeterLoginWindow *login_window,
+                      GdmLoginExtension     *extension)
+{
+        GtkWidget *container;
+        char *name;
+
+        name = gdm_login_extension_get_name (extension);
+        g_debug ("GdmGreeterLoginWindow: extension '%s' activated", name);
+        g_free (name);
+
+        container = g_object_get_data (G_OBJECT (extension),
+                                       "gdm-greeter-login-window-page-container");
+
+        if (container == NULL) {
+                GtkWidget *page;
+
+                container = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+                gtk_container_add (GTK_CONTAINER (login_window->priv->auth_page_box),
+                                   container);
+
+                page = gdm_login_extension_get_page (extension);
+                if (page != NULL) {
+                        gtk_container_add (GTK_CONTAINER (container), page);
+                        gtk_widget_show (page);
+                }
+                g_object_set_data (G_OBJECT (extension),
+                                   "gdm-greeter-login-window-page-container",
+                                   container);
+        }
+
+        gtk_widget_show (container);
+
+        login_window->priv->active_extension = extension;
+        switch_mode (login_window, login_window->priv->dialog_mode);
+}
+
+static void
+clear_active_extension (GdmGreeterLoginWindow *login_window)
+{
+
+        GtkWidget *container;
+        GtkActionGroup *actions;
+
+        if (login_window->priv->active_extension == NULL) {
+                return;
+        }
+
+        container = g_object_get_data (G_OBJECT (login_window->priv->active_extension),
+                                       "gdm-greeter-login-window-page-container");
+
+        if (container != NULL) {
+                gtk_widget_hide (container);
+        }
+
+        actions = gdm_login_extension_get_actions (login_window->priv->active_extension);
+
+        if (actions != NULL) {
+                gtk_action_group_set_sensitive (actions, FALSE);
+                gtk_action_group_set_visible (actions, FALSE);
+                g_object_unref (actions);
+        }
+
+        login_window->priv->active_extension = NULL;
+}
+
+static void
+reset_dialog (GdmGreeterLoginWindow *login_window,
+              guint                  dialog_mode)
+{
+        g_debug ("GdmGreeterLoginWindow: Resetting dialog to mode %u", dialog_mode);
+        set_busy (login_window);
+        set_sensitive (login_window, FALSE);
+
+        login_window->priv->num_queries = 0;
+
+        g_free (login_window->priv->service_name_of_session_ready_to_start);
+        login_window->priv->service_name_of_session_ready_to_start = NULL;
+
+        if (dialog_mode == MODE_SELECTION) {
+                if (login_window->priv->timed_login_enabled) {
+                        gdm_chooser_widget_set_item_timer (GDM_CHOOSER_WIDGET 
(login_window->priv->user_chooser),
+                                                           GDM_USER_CHOOSER_USER_AUTO, 0);
+                        remove_timed_login_timeout (login_window);
+                        login_window->priv->timed_login_enabled = FALSE;
+                }
+
+                g_signal_handlers_block_by_func (G_OBJECT (login_window->priv->user_chooser),
+                                                 G_CALLBACK (on_user_unchosen), login_window);
+                gdm_user_chooser_widget_set_chosen_user_name (GDM_USER_CHOOSER_WIDGET 
(login_window->priv->user_chooser), NULL);
+                g_signal_handlers_unblock_by_func (G_OBJECT (login_window->priv->user_chooser),
+                                                   G_CALLBACK (on_user_unchosen), login_window);
+
+                if (login_window->priv->start_session_handler_id > 0) {
+                        g_signal_handler_disconnect (login_window, 
login_window->priv->start_session_handler_id);
+                        login_window->priv->start_session_handler_id = 0;
+                }
+
+                set_message (login_window, "");
+        }
+
+        g_list_foreach (login_window->priv->extensions, (GFunc) reset_extension, login_window);
+
+        if (can_jump_to_authenticate (login_window)) {
+                /* If we don't have a user list jump straight to authenticate */
+                g_debug ("GdmGreeterLoginWindow: jumping straight to authenticate");
+                g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED],
+                               0, GDM_USER_CHOOSER_USER_OTHER);
+                begin_other_verification (login_window);
+        } else {
+                clear_active_extension (login_window);
+                switch_mode (login_window, dialog_mode);
+        }
+
+        gtk_widget_set_sensitive (login_window->priv->extension_list, TRUE);
+        set_ready (login_window);
+        set_focus (GDM_GREETER_LOGIN_WINDOW (login_window));
+        update_banner_message (login_window);
+
+        if (gdm_chooser_widget_get_number_of_items (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser)) 
= 1) {
+                gdm_chooser_widget_propagate_pending_key_events (GDM_CHOOSER_WIDGET 
(login_window->priv->user_chooser));
+        }
+}
+
+static void
+restart_conversations (GdmGreeterLoginWindow *login_window)
+{
+        set_busy (login_window);
+        set_sensitive (login_window, FALSE);
+        g_signal_emit (login_window, signals[CANCELLED], 0);
+}
+
+static gboolean
+has_queued_messages (GdmGreeterLoginWindow *login_window)
+{
+        GList *node;
+
+        node = login_window->priv->extensions;
+        while (node != NULL) {
+                GdmLoginExtension *extension;
+
+                extension = (GdmLoginExtension *) node->data;
+
+                if (gdm_login_extension_has_queued_messages (extension)) {
+                        return TRUE;
+                }
+                node = node->next;
+        }
+
+        return FALSE;
+}
+
+static void
+reset_dialog_after_messages (GdmGreeterLoginWindow *login_window,
+                             guint                  dialog_mode)
+{
+        if (has_queued_messages (login_window)) {
+                g_debug ("GdmGreeterLoginWindow: will reset dialog after pending messages");
+                login_window->priv->next_mode = dialog_mode;
+        } else {
+                g_debug ("GdmGreeterLoginWindow: resetting dialog");
+                reset_dialog (login_window, dialog_mode);
+        }
+
+}
+
+static void
+do_cancel (GdmGreeterLoginWindow *login_window)
+{
+        /* need to wait for response from backend */
+        set_message (login_window, _("Cancelling…"));
+        restart_conversations (login_window);
+        reset_dialog_after_messages (login_window, MODE_SELECTION);
+}
+
+gboolean
+gdm_greeter_login_window_ready (GdmGreeterLoginWindow *login_window,
+                                const char            *service_name)
+{
+        GdmLoginExtension *extension;
+
+        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
+
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension != NULL) {
+                if (!login_window->priv->user_chooser_loaded) {
+                        g_debug ("GdmGreeterLoginWindow: Ignoring daemon Ready event since not loaded yet");
+                        login_window->priv->extensions_to_enable = g_list_prepend 
(login_window->priv->extensions_to_enable,
+                                                                              extension);
+                        return TRUE;
+                } else if (login_window->priv->next_mode != MODE_UNDEFINED) {
+                        g_debug ("GdmGreeterLoginWindow: Ignoring daemon Ready event since still showing 
messages");
+                        login_window->priv->extensions_to_enable = g_list_prepend 
(login_window->priv->extensions_to_enable,
+                                                                              extension);
+                        return TRUE;
+                }
+
+                gdm_login_extension_set_ready (extension);
+        }
+
+        set_sensitive (GDM_GREETER_LOGIN_WINDOW (login_window), TRUE);
+        set_ready (GDM_GREETER_LOGIN_WINDOW (login_window));
+        set_focus (GDM_GREETER_LOGIN_WINDOW (login_window));
+        gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (login_window)));
+
+        /* If the user list is disabled, then start the PAM conversation */
+        if (can_jump_to_authenticate (login_window)) {
+                g_debug ("Starting PAM conversation since user list disabled or no local users");
+                g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED],
+                               0, GDM_USER_CHOOSER_USER_OTHER);
+                begin_other_verification (login_window);
+        }
+
+        return TRUE;
+}
+
+static void
+handle_stopped_conversation (GdmGreeterLoginWindow *login_window,
+                             const char            *service_name)
+{
+        GdmLoginExtension *extension;
+
+        /* If the password conversation failed, then start over
+         *
+         * FIXME: we need to get this policy out of the source code
+         */
+        if (strcmp (service_name, "gdm-password") == 0 ||
+            strcmp (service_name, "gdm") == 0) {
+                g_debug ("GdmGreeterLoginWindow: main conversation failed, starting over");
+                restart_conversations (login_window);
+                reset_dialog_after_messages (login_window, MODE_SELECTION);
+                return;
+        }
+
+        if (login_window->priv->dialog_mode == MODE_AUTHENTICATION) {
+                g_debug ("GdmGreeterLoginWindow: conversation failed, starting over");
+                restart_conversations (login_window);
+                reset_dialog_after_messages (login_window, MODE_AUTHENTICATION);
+                return;
+        } else if (login_window->priv->dialog_mode != MODE_MULTIPLE_AUTHENTICATION) {
+                g_warning ("conversation %s stopped when it shouldn't have been running (mode %d)",
+                           service_name, login_window->priv->dialog_mode);
+                restart_conversations (login_window);
+                return;
+        }
+
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension != NULL) {
+                gdm_login_extension_reset (extension);
+
+                login_window->priv->extensions_to_stop = g_list_remove 
(login_window->priv->extensions_to_stop, extension);
+        }
+
+        /* If every conversation has failed, then just start over.
+         */
+        extension = gdm_extension_list_get_active_extension (GDM_EXTENSION_LIST 
(login_window->priv->extension_list));
+
+        if (extension == NULL || !gdm_login_extension_is_enabled (extension)) {
+                g_debug ("GdmGreeterLoginWindow: No conversations left, starting over");
+                restart_conversations (login_window);
+                reset_dialog_after_messages (login_window, MODE_SELECTION);
+        }
+
+        if (extension != NULL) {
+                g_object_unref (extension);
+        }
+
+        update_extension_list_visibility (login_window);
+}
+
+gboolean
+gdm_greeter_login_window_conversation_stopped (GdmGreeterLoginWindow *login_window,
+                                               const char            *service_name)
+{
+        GdmLoginExtension *extension;
+
+        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
+
+        g_debug ("GdmGreeterLoginWindow: conversation '%s' has stopped", service_name);
+
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension != NULL && gdm_login_extension_is_enabled (extension)) {
+                if (gdm_login_extension_has_queued_messages (extension)) {
+                        login_window->priv->extensions_to_stop = g_list_prepend 
(login_window->priv->extensions_to_stop, extension);
+                } else {
+                        handle_stopped_conversation (login_window, service_name);
+                }
+        }
+
+        return TRUE;
+}
+
+static gboolean
+restart_extension_conversation (GdmLoginExtension     *extension,
+                                GdmGreeterLoginWindow *login_window)
+{
+        char *service_name;
+
+        login_window->priv->extensions_to_stop = g_list_remove (login_window->priv->extensions_to_stop, 
extension);
+
+        service_name = gdm_login_extension_get_service_name (extension);
+        if (service_name != NULL) {
+                char *name;
+
+                name = gdm_login_extension_get_name (extension);
+                g_debug ("GdmGreeterLoginWindow: restarting '%s' conversation", name);
+                g_free (name);
+
+                g_signal_emit (login_window, signals[START_CONVERSATION], 0, service_name);
+                g_free (service_name);
+        }
+
+        return FALSE;
+}
+
+gboolean
+gdm_greeter_login_window_reset (GdmGreeterLoginWindow *login_window)
+{
+        g_debug ("GdmGreeterLoginWindow: window reset");
+
+        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
+
+        reset_dialog_after_messages (login_window, MODE_SELECTION);
+        g_list_foreach (login_window->priv->extensions,
+                        (GFunc) restart_extension_conversation,
+                        login_window);
+
+        g_free (login_window->priv->service_name_of_session_ready_to_start);
+        login_window->priv->service_name_of_session_ready_to_start = NULL;
+
+        return TRUE;
+}
+
+gboolean
+gdm_greeter_login_window_info (GdmGreeterLoginWindow *login_window,
+                               const char            *service_name,
+                               const char            *text)
+{
+        GdmLoginExtension *extension;
+
+        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
+        g_debug ("GdmGreeterLoginWindow: info: %s", text);
+
+        maybe_show_cancel_button (login_window);
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension != NULL) {
+                gdm_login_extension_queue_message (extension,
+                                                     GDM_SERVICE_MESSAGE_TYPE_INFO,
+                                                     text);
+                show_extension_actions (extension);
+        }
+
+        return TRUE;
+}
+
+gboolean
+gdm_greeter_login_window_problem (GdmGreeterLoginWindow *login_window,
+                                  const char            *service_name,
+                                  const char            *text)
+{
+        GdmLoginExtension *extension;
+
+        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
+        g_debug ("GdmGreeterLoginWindow: problem: %s", text);
+        maybe_show_cancel_button (login_window);
+
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension != NULL) {
+                gdm_login_extension_queue_message (extension,
+                                                     GDM_SERVICE_MESSAGE_TYPE_PROBLEM,
+                                                     text);
+                show_extension_actions (extension);
+        }
+
+        return TRUE;
+}
+
+static void
+request_timed_login (GdmGreeterLoginWindow *login_window)
+{
+        g_debug ("GdmGreeterLoginWindow: requesting timed login");
+
+        gtk_widget_show (login_window->priv->user_chooser);
+
+        if (login_window->priv->dialog_mode != MODE_SELECTION) {
+                reset_dialog (login_window, MODE_SELECTION);
+        }
+
+        if (!login_window->priv->timed_login_already_enabled) {
+                gdm_user_chooser_widget_set_chosen_user_name (GDM_USER_CHOOSER_WIDGET 
(login_window->priv->user_chooser),
+                                                              GDM_USER_CHOOSER_USER_AUTO);
+        }
+
+        login_window->priv->timed_login_already_enabled = TRUE;
+}
+
+gboolean
+gdm_greeter_login_window_service_unavailable (GdmGreeterLoginWindow *login_window,
+                                              const char            *service_name)
+{
+        GdmLoginExtension *extension;
+
+        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
+        g_debug ("GdmGreeterLoginWindow: service unavailable: %s", service_name);
+
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension != NULL) {
+                GdmLoginExtension *active_extension;
+
+                gdm_login_extension_set_enabled (extension, FALSE);
+
+                active_extension = gdm_extension_list_get_active_extension (GDM_EXTENSION_LIST 
(login_window->priv->extension_list));
+
+                if (active_extension == extension) {
+                        restart_conversations (login_window);
+                }
+
+                if (active_extension != NULL) {
+                        g_object_unref (active_extension);
+                }
+        }
+
+        return TRUE;
+}
+
+void
+gdm_greeter_login_window_request_timed_login (GdmGreeterLoginWindow *login_window,
+                                              const char            *username,
+                                              int                    delay)
+{
+        g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window));
+
+        g_debug ("GdmGreeterLoginWindow: requested automatic login for user '%s' in %d seconds", username, 
delay);
+
+        g_free (login_window->priv->timed_login_username);
+        login_window->priv->timed_login_username = g_strdup (username);
+        login_window->priv->timed_login_delay = delay;
+
+        /* add the auto user right away so we won't trigger a mode
+           switch to authenticate when the user list is disabled */
+        gdm_user_chooser_widget_set_show_user_auto (GDM_USER_CHOOSER_WIDGET 
(login_window->priv->user_chooser), TRUE);
+
+        /* if the users aren't loaded then we'll handle it in when they are */
+        if (login_window->priv->user_chooser_loaded) {
+                g_debug ("Handling timed login request since users are already loaded.");
+                request_timed_login (login_window);
+        } else {
+                g_debug ("Waiting to handle timed login request until users are loaded.");
+        }
+}
+
+static void
+gdm_greeter_login_window_start_session (GdmGreeterLoginWindow *login_window)
+{
+        g_debug ("GdmGreeterLoginWindow: starting session");
+        g_signal_emit (login_window,
+                       signals[START_SESSION],
+                       0,
+                       login_window->priv->service_name_of_session_ready_to_start);
+        g_free (login_window->priv->service_name_of_session_ready_to_start);
+        login_window->priv->service_name_of_session_ready_to_start = NULL;
+}
+
+static void
+gdm_greeter_login_window_start_session_when_ready (GdmGreeterLoginWindow *login_window,
+                                                   const char            *service_name)
+{
+        GdmLoginExtension *extension;
+
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        login_window->priv->service_name_of_session_ready_to_start = g_strdup (service_name);
+
+        if (!gdm_login_extension_has_queued_messages (extension)) {
+                g_debug ("GdmGreeterLoginWindow: starting session");
+                g_signal_emit (login_window, signals[START_SESSION], 0, service_name);
+                gdm_greeter_login_window_start_session (login_window);
+        }
+}
+
+gboolean
+gdm_greeter_login_window_info_query (GdmGreeterLoginWindow *login_window,
+                                     const char            *service_name,
+                                     const char            *text)
+{
+        GdmLoginExtension *extension;
+
+        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
+
+        login_window->priv->num_queries++;
+        maybe_show_cancel_button (login_window);
+
+        g_debug ("GdmGreeterLoginWindow: info query: %s", text);
+
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension != NULL) {
+                gdm_login_extension_ask_question (extension, text);
+        }
+
+        set_log_in_button_mode (login_window, LOGIN_BUTTON_ANSWER_QUERY);
+        set_sensitive (GDM_GREETER_LOGIN_WINDOW (login_window), TRUE);
+        set_ready (GDM_GREETER_LOGIN_WINDOW (login_window));
+        set_focus (GDM_GREETER_LOGIN_WINDOW (login_window));
+
+        gdm_chooser_widget_propagate_pending_key_events (GDM_CHOOSER_WIDGET 
(login_window->priv->user_chooser));
+
+        return TRUE;
+}
+
+gboolean
+gdm_greeter_login_window_secret_info_query (GdmGreeterLoginWindow *login_window,
+                                            const char            *service_name,
+                                            const char            *text)
+{
+
+        GdmLoginExtension *extension;
+
+        g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE);
+
+        login_window->priv->num_queries++;
+        maybe_show_cancel_button (login_window);
+
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension != NULL) {
+                gdm_login_extension_ask_secret (extension, text);
+        }
+
+        set_log_in_button_mode (login_window, LOGIN_BUTTON_ANSWER_QUERY);
+        set_sensitive (GDM_GREETER_LOGIN_WINDOW (login_window), TRUE);
+        set_ready (GDM_GREETER_LOGIN_WINDOW (login_window));
+        set_focus (GDM_GREETER_LOGIN_WINDOW (login_window));
+
+        gdm_chooser_widget_propagate_pending_key_events (GDM_CHOOSER_WIDGET 
(login_window->priv->user_chooser));
+
+        return TRUE;
+}
+
+void
+gdm_greeter_login_window_session_opened (GdmGreeterLoginWindow *login_window,
+                                          const char            *service_name)
+{
+        g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window));
+
+        g_debug ("GdmGreeterLoginWindow: session now opened via service %s",
+                 service_name);
+
+        gdm_greeter_login_window_start_session_when_ready (login_window,
+                                                           service_name);
+}
+
+static void
+_gdm_greeter_login_window_set_display_is_local (GdmGreeterLoginWindow *login_window,
+                                                gboolean               is)
+{
+        login_window->priv->display_is_local = is;
+}
+
+static void
+gdm_greeter_login_window_set_property (GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+        GdmGreeterLoginWindow *self;
+
+        self = GDM_GREETER_LOGIN_WINDOW (object);
+
+        switch (prop_id) {
+        case PROP_DISPLAY_IS_LOCAL:
+                _gdm_greeter_login_window_set_display_is_local (self, g_value_get_boolean (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gdm_greeter_login_window_get_property (GObject    *object,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+        GdmGreeterLoginWindow *self;
+
+        self = GDM_GREETER_LOGIN_WINDOW (object);
+
+        switch (prop_id) {
+        case PROP_DISPLAY_IS_LOCAL:
+                g_value_set_boolean (value, self->priv->display_is_local);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+cancel_button_clicked (GtkButton             *button,
+                       GdmGreeterLoginWindow *login_window)
+{
+        do_cancel (login_window);
+}
+
+static void
+on_user_chooser_visibility_changed (GdmGreeterLoginWindow *login_window)
+{
+        g_debug ("GdmGreeterLoginWindow: Chooser visibility changed");
+        update_banner_message (login_window);
+}
+
+static gboolean
+begin_extension_verification_for_selected_user (GdmLoginExtension     *extension,
+                                                GdmGreeterLoginWindow *login_window)
+{
+        char *user_name;
+        char *service_name;
+
+        user_name = gdm_user_chooser_widget_get_chosen_user_name (GDM_USER_CHOOSER_WIDGET 
(login_window->priv->user_chooser));
+
+        if (user_name == NULL) {
+                return TRUE;
+        }
+
+        service_name = gdm_login_extension_get_service_name (extension);
+        if (service_name != NULL) {
+                g_signal_emit (login_window, signals[BEGIN_VERIFICATION_FOR_USER], 0, service_name, 
user_name);
+                g_free (service_name);
+        }
+
+        gdm_extension_list_add_extension (GDM_EXTENSION_LIST (login_window->priv->extension_list),
+                                          extension);
+
+        g_free (user_name);
+        return FALSE;
+}
+
+static void
+enable_waiting_extensions (GdmGreeterLoginWindow *login_window)
+{
+        GList *node;
+
+        node = login_window->priv->extensions_to_enable;
+        while (node != NULL) {
+                GdmLoginExtension *extension;
+
+                extension = GDM_LOGIN_EXTENSION (node->data);
+
+                gdm_login_extension_set_ready (extension);
+
+                node = node->next;
+        }
+
+        login_window->priv->extensions_to_enable = NULL;
+}
+
+static void
+on_users_loaded (GdmUserChooserWidget  *user_chooser,
+                 GdmGreeterLoginWindow *login_window)
+{
+        g_debug ("GdmGreeterLoginWindow: users loaded");
+        login_window->priv->user_chooser_loaded = TRUE;
+
+        update_banner_message (login_window);
+
+        gtk_widget_show (login_window->priv->user_chooser);
+
+        enable_waiting_extensions (login_window);
+
+        if (login_window->priv->timed_login_username != NULL
+            && !login_window->priv->timed_login_already_enabled) {
+                request_timed_login (login_window);
+        } else if (can_jump_to_authenticate (login_window)) {
+
+                gtk_widget_hide (login_window->priv->user_chooser);
+
+                /* jump straight to authenticate */
+                g_debug ("GdmGreeterLoginWindow: jumping straight to authenticate");
+                g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED],
+                               0, GDM_USER_CHOOSER_USER_OTHER);
+                begin_other_verification (login_window);
+        }
+}
+
+static void
+choose_user (GdmGreeterLoginWindow *login_window,
+             const char            *user_name)
+{
+        GdmLoginExtension *extension;
+
+        g_assert (user_name != NULL);
+        g_debug ("GdmGreeterLoginWindow: user chosen '%s'", user_name);
+
+        g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED],
+                       0, user_name);
+
+        g_list_foreach (login_window->priv->extensions,
+                        (GFunc) begin_extension_verification_for_selected_user,
+                        login_window);
+
+        extension = gdm_extension_list_get_active_extension (GDM_EXTENSION_LIST 
(login_window->priv->extension_list));
+        set_extension_active (login_window, extension);
+        g_object_unref (extension);
+
+        switch_mode (login_window, MODE_MULTIPLE_AUTHENTICATION);
+        update_extension_list_visibility (login_window);
+}
+
+static void
+begin_auto_login (GdmGreeterLoginWindow *login_window)
+{
+        g_signal_emit (login_window, signals[BEGIN_AUTO_LOGIN], 0,
+                       login_window->priv->timed_login_username);
+
+        login_window->priv->timed_login_enabled = TRUE;
+        restart_timed_login_timeout (login_window);
+
+        /* just wait for the user to select language and stuff */
+        set_message (login_window, _("Select language and click Log In"));
+
+        clear_active_extension (login_window);
+        switch_mode (login_window, MODE_TIMED_LOGIN);
+
+        show_widget (login_window, "conversation-list", FALSE);
+        g_list_foreach (login_window->priv->extensions,
+                        (GFunc) reset_extension,
+                        login_window);
+}
+
+static void
+reset_extension_if_not_given (GdmLoginExtension  *extension,
+                              GdmLoginExtension  *given_extension)
+{
+        if (extension == given_extension) {
+                return;
+        }
+
+        gdm_login_extension_reset (extension);
+}
+
+static void
+reset_every_extension_but_given_extension (GdmGreeterLoginWindow *login_window,
+                                 GdmLoginExtension   *extension)
+{
+        g_list_foreach (login_window->priv->extensions,
+                        (GFunc) reset_extension_if_not_given,
+                        extension);
+
+}
+
+static void
+begin_single_service_verification (GdmGreeterLoginWindow *login_window,
+                                   const char            *service_name)
+{
+        GdmLoginExtension *extension;
+
+        extension = find_extension_with_service_name (login_window, service_name);
+
+        if (extension == NULL) {
+                g_debug ("GdmGreeterLoginWindow: %s has no extension associated with it", service_name);
+                return;
+        }
+
+        g_debug ("GdmGreeterLoginWindow: Beginning %s auth conversation", service_name);
+
+        /* FIXME: we should probably give the plugin more say for
+         * what happens here.
+         */
+        g_signal_emit (login_window, signals[BEGIN_VERIFICATION], 0, service_name);
+
+        reset_every_extension_but_given_extension (login_window, extension);
+
+        set_extension_active (login_window, extension);
+        switch_mode (login_window, MODE_AUTHENTICATION);
+
+        show_widget (login_window, "conversation-list", FALSE);
+}
+
+static void
+on_user_chooser_activated (GdmUserChooserWidget  *user_chooser,
+                           GdmGreeterLoginWindow *login_window)
+{
+        char *user_name;
+        char *item_id;
+
+        user_name = gdm_user_chooser_widget_get_chosen_user_name (GDM_USER_CHOOSER_WIDGET 
(login_window->priv->user_chooser));
+
+        if (user_name != NULL) {
+                g_debug ("GdmGreeterLoginWindow: user chosen '%s'", user_name);
+                choose_user (login_window, user_name);
+                g_free (user_name);
+                return;
+        }
+
+        item_id = gdm_chooser_widget_get_active_item (GDM_CHOOSER_WIDGET (user_chooser));
+        g_debug ("GdmGreeterLoginWindow: item chosen '%s'", item_id);
+
+        g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED],
+                       0, item_id);
+
+        if (strcmp (item_id, GDM_USER_CHOOSER_USER_OTHER) == 0) {
+                g_debug ("GdmGreeterLoginWindow: Starting all auth conversations");
+                g_free (item_id);
+
+                begin_other_verification (login_window);
+        } else if (strcmp (item_id, GDM_USER_CHOOSER_USER_GUEST) == 0) {
+                /* FIXME: handle guest account stuff */
+                g_free (item_id);
+        } else if (strcmp (item_id, GDM_USER_CHOOSER_USER_AUTO) == 0) {
+                g_debug ("GdmGreeterLoginWindow: Starting auto login");
+                g_free (item_id);
+
+                begin_auto_login (login_window);
+        } else {
+                g_debug ("GdmGreeterLoginWindow: Starting single auth conversation");
+                begin_single_service_verification (login_window, item_id);
+                g_free (item_id);
+        }
+}
+
+static void
+on_user_unchosen (GdmUserChooserWidget  *user_chooser,
+                  GdmGreeterLoginWindow *login_window)
+{
+        do_cancel (login_window);
+}
+
+static void
+on_session_activated (GdmSessionOptionWidget *session_option_widget,
+                      GdmGreeterLoginWindow  *login_window)
+{
+        char *session;
+
+        session = gdm_session_option_widget_get_current_session (GDM_SESSION_OPTION_WIDGET 
(login_window->priv->session_option_widget));
+        if (session == NULL) {
+                return;
+        }
+
+        g_signal_emit (login_window, signals[SESSION_SELECTED], 0, session);
+
+        g_free (session);
+}
+
+void
+gdm_greeter_login_window_set_default_session_name (GdmGreeterLoginWindow *login_window,
+                                                   const char            *session_name)
+{
+        g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window));
+
+        if (session_name != NULL && !gdm_option_widget_lookup_item (GDM_OPTION_WIDGET 
(login_window->priv->session_option_widget),
+                                            session_name, NULL, NULL, NULL)) {
+                if (strcmp (session_name, GDM_CUSTOM_SESSION) == 0) {
+                        gdm_option_widget_add_item (GDM_OPTION_WIDGET 
(login_window->priv->session_option_widget),
+                                                    GDM_CUSTOM_SESSION,
+                                                    C_("customsession", "Custom"),
+                                                    _("Custom session"),
+                                                    GDM_OPTION_WIDGET_POSITION_TOP);
+                } else {
+                        g_warning ("Default session is not available");
+                        return;
+                }
+        }
+
+        gdm_option_widget_set_default_item (GDM_OPTION_WIDGET (login_window->priv->session_option_widget),
+                                            session_name);
+}
+
+static void
+rotate_computer_info (GdmGreeterLoginWindow *login_window)
+{
+        GtkWidget *notebook;
+        int        current_page;
+        int        n_pages;
+
+        /* switch page */
+        notebook = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, 
"computer-info-notebook"));
+        current_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
+        n_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook));
+
+        if (current_page + 1 < n_pages) {
+                gtk_notebook_next_page (GTK_NOTEBOOK (notebook));
+        } else {
+                gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), 0);
+        }
+
+}
+
+static gboolean
+on_computer_info_label_button_press (GtkWidget             *widget,
+                                     GdkEventButton        *event,
+                                     GdmGreeterLoginWindow *login_window)
+{
+        rotate_computer_info (login_window);
+        return FALSE;
+}
+
+static char *
+file_read_one_line (const char *filename)
+{
+        FILE *f;
+        char *line;
+        char buf[4096];
+
+        line = NULL;
+
+        f = fopen (filename, "r");
+        if (f == NULL) {
+                g_warning ("Unable to open file %s: %s", filename, g_strerror (errno));
+                goto out;
+        }
+
+        if (fgets (buf, sizeof (buf), f) == NULL) {
+                g_warning ("Unable to read from file %s", filename);
+                goto out;
+        }
+
+        line = g_strdup (buf);
+        g_strchomp (line);
+
+ out:
+        fclose (f);
+
+        return line;
+}
+
+static const char *known_etc_info_files [] = {
+        "redhat-release",
+        "SuSE-release",
+        "gentoo-release",
+        "arch-release",
+        "debian_version",
+        "mandriva-release",
+        "slackware-version",
+        "system-release",
+        NULL
+};
+
+
+static char *
+get_system_version (void)
+{
+        char *version;
+        char *output;
+        int i;
+
+        version = NULL;
+
+        output = NULL;
+        if (g_spawn_command_line_sync (LSB_RELEASE_COMMAND, &output, NULL, NULL, NULL)) {
+                if (g_str_has_prefix (output, "Description:")) {
+                        version = g_strdup (output + strlen ("Description:"));
+                } else {
+                        version = g_strdup (output);
+                }
+                version = g_strstrip (version);
+
+                /* lsb_release returns (none) if it doesn't know,
+                 * so return NULL in that case */
+                if (strcmp (version, "(none)") == 0) {
+                        g_free (version);
+                        version = NULL;
+                }
+
+                g_free (output);
+
+                goto out;
+        }
+
+        for (i = 0; known_etc_info_files [i]; i++) {
+                char *path1;
+                char *path2;
+
+                path1 = g_build_filename (SYSCONFDIR, known_etc_info_files [i], NULL);
+                path2 = g_build_filename ("/etc", known_etc_info_files [i], NULL);
+                if (g_access (path1, R_OK) == 0) {
+                        version = file_read_one_line (path1);
+                } else if (g_access (path2, R_OK) == 0) {
+                        version = file_read_one_line (path2);
+                }
+                g_free (path2);
+                g_free (path1);
+                if (version != NULL) {
+                        break;
+                }
+        }
+
+        if (version == NULL) {
+                output = NULL;
+                if (g_spawn_command_line_sync ("uname -sr", &output, NULL, NULL, NULL)) {
+                        version = g_strchomp (output);
+                }
+        }
+ out:
+        return version;
+}
+
+static void
+create_computer_info (GdmGreeterLoginWindow *login_window)
+{
+        GtkWidget *label;
+
+        gdm_profile_start (NULL);
+
+        label = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, 
"computer-info-name-label"));
+        if (label != NULL) {
+                char localhost[HOST_NAME_MAX + 1] = "";
+
+                if (gethostname (localhost, HOST_NAME_MAX) == 0) {
+                        gtk_label_set_text (GTK_LABEL (label), localhost);
+                }
+
+                /* If this isn't actually unique identifier for the computer, then
+                 * don't bother showing it by default.
+                 */
+                if (strcmp (localhost, "localhost") == 0 ||
+                    strcmp (localhost, "localhost.localdomain") == 0) {
+
+                    rotate_computer_info (login_window);
+                }
+        }
+
+        label = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, 
"computer-info-version-label"));
+        if (label != NULL) {
+                char *version;
+                version = get_system_version ();
+                gtk_label_set_text (GTK_LABEL (label), version);
+                g_free (version);
+        }
+
+        gdm_profile_end (NULL);
+}
+
+#define INVISIBLE_CHAR_DEFAULT       '*'
+#define INVISIBLE_CHAR_BLACK_CIRCLE  0x25cf
+#define INVISIBLE_CHAR_WHITE_BULLET  0x25e6
+#define INVISIBLE_CHAR_BULLET        0x2022
+#define INVISIBLE_CHAR_NONE          0
+
+static void
+on_extension_activated (GdmGreeterLoginWindow *login_window,
+                        GdmLoginExtension   *extension)
+{
+        set_extension_active (login_window, extension);
+}
+
+static void
+on_extension_deactivated (GdmGreeterLoginWindow *login_window,
+                          GdmLoginExtension   *extension)
+{
+        char *name;
+
+        if (login_window->priv->active_extension != extension) {
+                g_warning ("inactive extension has been deactivated");
+                return;
+        }
+
+        name = gdm_login_extension_get_name (extension);
+        g_debug ("GdmGreeterLoginWindow: extension '%s' now in background", name);
+        g_free (name);
+
+        clear_active_extension (login_window);
+
+        login_window->priv->active_extension = gdm_extension_list_get_active_extension (GDM_EXTENSION_LIST 
(login_window->priv->extension_list));
+        g_object_unref (login_window->priv->active_extension);
+}
+
+static void
+register_custom_types (GdmGreeterLoginWindow *login_window)
+{
+        GType types[] = { GDM_TYPE_USER_CHOOSER_WIDGET,
+                          GDM_TYPE_SESSION_OPTION_WIDGET,
+                          GDM_TYPE_EXTENSION_LIST };
+        int i;
+
+        for (i = 0; i < G_N_ELEMENTS (types); i++) {
+                g_debug ("Registering type '%s'", g_type_name (types[i]));
+        }
+}
+
+static void
+load_theme (GdmGreeterLoginWindow *login_window)
+{
+        GtkWidget *button;
+        GtkWidget *box;
+        GtkWidget *image;
+        GError* error = NULL;
+
+        gdm_profile_start (NULL);
+
+        register_custom_types (login_window);
+
+        login_window->priv->builder = gtk_builder_new ();
+        if (!gtk_builder_add_from_file (login_window->priv->builder, UIDIR "/" UI_XML_FILE, &error)) {
+                g_warning ("Couldn't load builder file: %s", error->message);
+                g_error_free (error);
+        }
+
+        g_assert (login_window->priv->builder != NULL);
+
+        image = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "logo-image"));
+        if (image != NULL) {
+                GdkPixbuf *pixbuf;
+                char *path;
+
+                path = g_settings_get_string (login_window->priv->settings, KEY_LOGO);
+                g_debug ("GdmGreeterLoginWindow: Got greeter logo '%s'", path);
+
+                pixbuf = gdk_pixbuf_new_from_file_at_scale (path, -1, 48, TRUE, NULL);
+                g_free (path);
+
+                if (pixbuf != NULL) {
+                        gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
+                        g_object_unref (pixbuf);
+                }
+        }
+
+        box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "window-frame"));
+        gtk_container_add (GTK_CONTAINER (login_window), box);
+        gtk_widget_grab_default(GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder,
+                                                                    "log-in-button")));
+
+        login_window->priv->user_chooser = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, 
"user-chooser"));
+
+        gdm_user_chooser_widget_set_show_only_chosen (GDM_USER_CHOOSER_WIDGET 
(login_window->priv->user_chooser), TRUE);
+
+        g_signal_connect (login_window->priv->user_chooser,
+                          "loaded",
+                          G_CALLBACK (on_users_loaded),
+                          login_window);
+        g_signal_connect (login_window->priv->user_chooser,
+                          "activated",
+                          G_CALLBACK (on_user_chooser_activated),
+                          login_window);
+        g_signal_connect (login_window->priv->user_chooser,
+                          "deactivated",
+                          G_CALLBACK (on_user_unchosen),
+                          login_window);
+
+        g_signal_connect_swapped (login_window->priv->user_chooser,
+                                 "notify::list-visible",
+                                 G_CALLBACK (on_user_chooser_visibility_changed),
+                                 login_window);
+
+        login_window->priv->session_option_widget = GTK_WIDGET (gtk_builder_get_object 
(login_window->priv->builder, "session-option-widget"));
+
+        g_signal_connect (login_window->priv->session_option_widget,
+                          "activated",
+                          G_CALLBACK (on_session_activated),
+                          login_window);
+
+        login_window->priv->extension_list = GTK_WIDGET (gtk_builder_get_object 
(login_window->priv->builder, "extension-list"));
+
+        g_signal_connect_swapped (GDM_EXTENSION_LIST (login_window->priv->extension_list),
+                                  "activated",
+                                  G_CALLBACK (on_extension_activated),
+                                  login_window);
+        g_signal_connect_swapped (GDM_EXTENSION_LIST (login_window->priv->extension_list),
+                                  "deactivated",
+                                  G_CALLBACK (on_extension_deactivated),
+                                  login_window);
+
+        login_window->priv->auth_banner_label = GTK_WIDGET (gtk_builder_get_object 
(login_window->priv->builder, "auth-banner-label"));
+        /*make_label_small_italic (login_window->priv->auth_banner_label);*/
+        login_window->priv->auth_page_box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, 
"auth-page-box"));
+
+        button = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "cancel-button"));
+        g_signal_connect (button, "clicked", G_CALLBACK (cancel_button_clicked), login_window);
+
+        create_computer_info (login_window);
+
+        box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "computer-info-event-box"));
+        g_signal_connect (box, "button-press-event", G_CALLBACK (on_computer_info_label_button_press), 
login_window);
+
+        clear_active_extension (login_window);
+        switch_mode (login_window, MODE_SELECTION);
+
+        gdm_profile_end (NULL);
+}
+
+static gboolean
+gdm_greeter_login_window_key_press_event (GtkWidget   *widget,
+                                          GdkEventKey *event)
+{
+        GdmGreeterLoginWindow *login_window;
+
+        login_window = GDM_GREETER_LOGIN_WINDOW (widget);
+
+        if (event->keyval == GDK_KEY_Escape) {
+                if (login_window->priv->dialog_mode == MODE_AUTHENTICATION
+                    || login_window->priv->dialog_mode == MODE_TIMED_LOGIN) {
+                        do_cancel (GDM_GREETER_LOGIN_WINDOW (widget));
+                }
+        }
+
+        return GTK_WIDGET_CLASS (gdm_greeter_login_window_parent_class)->key_press_event (widget, event);
+}
+
+static void
+gdm_greeter_login_window_get_preferred_width (GtkWidget *widget,
+                                              gint      *minimum_size,
+                                              gint      *natural_size)
+{
+        int             monitor;
+        GdkScreen      *screen;
+        GdkWindow      *window;
+        GdkRectangle    area;
+        GtkAllocation   widget_allocation;
+        int             min_size;
+        int             nat_size;
+
+        gtk_widget_get_preferred_width (gtk_bin_get_child (GTK_BIN (widget)),
+                                        &min_size,
+                                        &nat_size);
+
+        /* Make width be at least 33% screen width */
+        screen = gtk_widget_get_screen (widget);
+        window = gtk_widget_get_window (widget);
+        if (window == NULL) {
+                window = gdk_screen_get_root_window (screen);
+        }
+        monitor = gdk_screen_get_monitor_at_window (screen, window);
+        gdk_screen_get_monitor_geometry (screen, monitor, &area);
+        min_size = MAX (min_size, .33 * area.width);
+        nat_size = MAX (nat_size, .33 * area.width);
+
+       /* Don't ever shrink window width */
+        gtk_widget_get_allocation (widget, &widget_allocation);
+
+        min_size = MAX (min_size, widget_allocation.width);
+        nat_size = MAX (nat_size, widget_allocation.width);
+
+        if (minimum_size)
+                *minimum_size = min_size;
+        if (natural_size)
+                *natural_size = nat_size;
+}
+
+static void
+gdm_greeter_login_window_get_preferred_height (GtkWidget *widget,
+                                               gint      *minimum_size,
+                                               gint      *natural_size)
+{
+        int             monitor;
+        GdkScreen      *screen;
+        GdkWindow      *window;
+        GdkRectangle    area;
+        int             min_size;
+        int             nat_size;
+
+        gtk_widget_get_preferred_height (gtk_bin_get_child (GTK_BIN (widget)),
+                                        &min_size,
+                                        &nat_size);
+
+        /* Make height be at most 80% of screen height */
+        screen = gtk_widget_get_screen (widget);
+        window = gtk_widget_get_window (widget);
+        if (window == NULL) {
+                window = gdk_screen_get_root_window (screen);
+        }
+        monitor = gdk_screen_get_monitor_at_window (screen, window);
+        gdk_screen_get_monitor_geometry (screen, monitor, &area);
+        min_size = MIN (min_size, .8 * area.height);
+        nat_size = MIN (nat_size, .8 * area.height);
+
+        if (minimum_size)
+                *minimum_size = min_size;
+        if (natural_size)
+                *natural_size = nat_size;
+}
+
+static void
+update_banner_message (GdmGreeterLoginWindow *login_window)
+{
+        gboolean     enabled;
+
+        if (login_window->priv->auth_banner_label == NULL) {
+                /* if the theme doesn't have a banner message */
+                g_debug ("GdmGreeterLoginWindow: theme doesn't support a banner message");
+                return;
+        }
+
+        enabled = g_settings_get_boolean (login_window->priv->settings, KEY_BANNER_MESSAGE_ENABLED);
+
+        login_window->priv->banner_message_enabled = enabled;
+
+        if (! enabled) {
+                g_debug ("GdmGreeterLoginWindow: banner message disabled");
+                gtk_widget_hide (login_window->priv->auth_banner_label);
+        } else {
+                char *message;
+
+                message = g_settings_get_string (login_window->priv->settings,
+                                                 KEY_BANNER_MESSAGE_TEXT);
+
+                if (message != NULL) {
+                        char *markup;
+                        markup = g_markup_printf_escaped ("<small><i>%s</i></small>", message);
+                        gtk_label_set_markup (GTK_LABEL (login_window->priv->auth_banner_label),
+                                              markup);
+                        g_free (markup);
+                }
+                g_debug ("GdmGreeterLoginWindow: banner message: %s", message);
+
+                gtk_widget_show (login_window->priv->auth_banner_label);
+        }
+}
+
+static GObject *
+gdm_greeter_login_window_constructor (GType                  type,
+                                      guint                  n_construct_properties,
+                                      GObjectConstructParam *construct_properties)
+{
+        GdmGreeterLoginWindow      *login_window;
+
+        gdm_profile_start (NULL);
+
+        login_window = GDM_GREETER_LOGIN_WINDOW (G_OBJECT_CLASS 
(gdm_greeter_login_window_parent_class)->constructor (type,
+                                                                                                             
         n_construct_properties,
+                                                                                                             
         construct_properties));
+
+
+        load_theme (login_window);
+        update_banner_message (login_window);
+
+        gdm_profile_end (NULL);
+
+        return G_OBJECT (login_window);
+}
+
+static void
+gdm_greeter_login_window_class_init (GdmGreeterLoginWindowClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+        GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+        object_class->get_property = gdm_greeter_login_window_get_property;
+        object_class->set_property = gdm_greeter_login_window_set_property;
+        object_class->constructor = gdm_greeter_login_window_constructor;
+        object_class->finalize = gdm_greeter_login_window_finalize;
+
+        widget_class->key_press_event = gdm_greeter_login_window_key_press_event;
+        widget_class->get_preferred_width = gdm_greeter_login_window_get_preferred_width;
+        widget_class->get_preferred_height = gdm_greeter_login_window_get_preferred_height;
+
+        gtk_container_class_handle_border_width (container_class);
+
+        signals [START_CONVERSATION] =
+                g_signal_new ("start-conversation",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, start_conversation),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__STRING,
+                              G_TYPE_NONE, 1, G_TYPE_STRING);
+        signals [BEGIN_AUTO_LOGIN] =
+                g_signal_new ("begin-auto-login",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, begin_auto_login),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__STRING,
+                              G_TYPE_NONE, 1, G_TYPE_STRING);
+        signals [BEGIN_VERIFICATION] =
+                g_signal_new ("begin-verification",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, begin_verification),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__STRING,
+                              G_TYPE_NONE,
+                              1, G_TYPE_STRING);
+        signals [BEGIN_VERIFICATION_FOR_USER] =
+                g_signal_new ("begin-verification-for-user",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, begin_verification_for_user),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_generic,
+                              G_TYPE_NONE,
+                              2, G_TYPE_STRING, G_TYPE_STRING);
+        signals [QUERY_ANSWER] =
+                g_signal_new ("query-answer",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, query_answer),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_generic,
+                              G_TYPE_NONE,
+                              2, G_TYPE_STRING, G_TYPE_STRING);
+        signals [USER_SELECTED] =
+                g_signal_new ("user-selected",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, user_selected),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__STRING,
+                              G_TYPE_NONE,
+                              1, G_TYPE_STRING);
+        signals [SESSION_SELECTED] =
+                g_signal_new ("session-selected",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, session_selected),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__STRING,
+                              G_TYPE_NONE,
+                              1, G_TYPE_STRING);
+        signals [CANCELLED] =
+                g_signal_new ("cancelled",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, cancelled),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE,
+                              0);
+        signals [START_SESSION] =
+                g_signal_new ("start-session",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, start_session),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__STRING,
+                              G_TYPE_NONE,
+                              1, G_TYPE_STRING);
+
+        g_object_class_install_property (object_class,
+                                         PROP_DISPLAY_IS_LOCAL,
+                                         g_param_spec_boolean ("display-is-local",
+                                                               "display is local",
+                                                               "display is local",
+                                                               FALSE,
+                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+        g_type_class_add_private (klass, sizeof (GdmGreeterLoginWindowPrivate));
+}
+
+static void
+on_gsettings_key_changed (GSettings             *settings,
+                          gchar                 *key,
+                          gpointer               user_data)
+{
+        GdmGreeterLoginWindow *login_window;
+
+        login_window = GDM_GREETER_LOGIN_WINDOW (user_data);
+
+        if (strcmp (key, KEY_BANNER_MESSAGE_ENABLED) == 0) {
+                gboolean enabled;
+
+                enabled = g_settings_get_boolean (settings, key);
+
+                g_debug ("setting key %s = %d", key, enabled);
+
+                login_window->priv->banner_message_enabled = enabled;
+                update_banner_message (login_window);
+
+        } else if (strcmp (key, KEY_BANNER_MESSAGE_TEXT) == 0) {
+                if (login_window->priv->banner_message_enabled) {
+                        update_banner_message (login_window);
+                }
+        } else {
+                g_debug ("GdmGreeterLoginWindow: Config key not handled: %s", key);
+        }
+}
+
+static void
+on_login_extension_answer (GdmGreeterLoginWindow *login_window,
+                             const char            *text,
+                             GdmLoginExtension   *extension)
+{
+        if (text != NULL) {
+                char *service_name;
+
+                service_name = gdm_login_extension_get_service_name (extension);
+                if (service_name != NULL) {
+                        g_signal_emit (login_window, signals[QUERY_ANSWER], 0, service_name, text);
+                        g_free (service_name);
+                }
+        }
+
+        set_sensitive (login_window, TRUE);
+        set_ready (login_window);
+}
+
+static void
+on_login_extension_cancel (GdmGreeterLoginWindow *login_window,
+                             GdmLoginExtension   *extension)
+{
+        restart_conversations (login_window);
+}
+
+static gboolean
+on_login_extension_chose_user (GdmGreeterLoginWindow *login_window,
+                                 const char            *username,
+                                 GdmLoginExtension   *extension)
+{
+        if (!login_window->priv->user_chooser_loaded) {
+                char *name;
+
+                name = gdm_login_extension_get_name (extension);
+                g_warning ("Task %s is trying to choose user before list is loaded", name);
+                g_free (name);
+                return FALSE;
+        }
+
+        /* If we're already authenticating then we can't pick a user
+         */
+        if (login_window->priv->dialog_mode == MODE_AUTHENTICATION || login_window->priv->dialog_mode == 
MODE_MULTIPLE_AUTHENTICATION) {
+                return FALSE;
+        }
+
+        gdm_user_chooser_widget_set_chosen_user_name (GDM_USER_CHOOSER_WIDGET 
(login_window->priv->user_chooser),
+                                                      username);
+
+        return TRUE;
+}
+
+static void
+on_login_extension_message_queue_empty (GdmGreeterLoginWindow *login_window,
+                                          GdmLoginExtension   *extension)
+{
+        gboolean needs_to_be_stopped;
+
+        needs_to_be_stopped = g_list_find (login_window->priv->extensions_to_stop, extension) != NULL;
+
+        if (needs_to_be_stopped) {
+                char *service_name;
+
+                service_name = gdm_login_extension_get_service_name (extension);
+                handle_stopped_conversation (login_window, service_name);
+                g_free (service_name);
+        }
+
+        if (login_window->priv->service_name_of_session_ready_to_start != NULL) {
+                if (login_window->priv->active_extension == extension) {
+                        gdm_greeter_login_window_start_session (login_window);
+                }
+        } else if (login_window->priv->next_mode != MODE_UNDEFINED) {
+                reset_dialog_after_messages (login_window, login_window->priv->next_mode);
+        }
+}
+
+static void
+on_button_action_label_changed (GtkWidget *button)
+{
+        GtkAction *action;
+        char *text;
+
+        action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (button));
+
+        g_object_get (G_OBJECT (action), "label", &text, NULL);
+
+        gtk_button_set_label (GTK_BUTTON (button), text);
+        g_free (text);
+}
+
+static void
+on_button_action_icon_name_changed (GtkWidget *button)
+{
+        GtkAction *action;
+        GtkWidget *image;
+
+        action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (button));
+
+        if (gtk_action_get_is_important (action)) {
+                image = gtk_action_create_icon (GTK_ACTION (action), GTK_ICON_SIZE_BUTTON);
+        } else {
+                image = NULL;
+        }
+
+        gtk_button_set_image (GTK_BUTTON (button), image);
+
+}
+
+static void
+on_button_action_tooltip_changed (GtkWidget *button)
+{
+        GtkAction *action;
+        char *text;
+
+        action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (button));
+
+        g_object_get (G_OBJECT (action), "tooltip", &text, NULL);
+
+        gtk_widget_set_tooltip_text (button, text);
+        g_free (text);
+}
+
+static GtkWidget *
+create_button_from_action (GtkAction *action)
+{
+        GtkWidget *button;
+
+        button = gtk_button_new ();
+
+        gtk_activatable_set_related_action (GTK_ACTIVATABLE (button), action);
+
+        g_signal_connect_swapped (action,
+                                  "notify::label",
+                                  G_CALLBACK (on_button_action_label_changed),
+                                  button);
+        g_signal_connect_swapped (action,
+                                  "notify::icon-name",
+                                  G_CALLBACK (on_button_action_icon_name_changed),
+                                  button);
+        g_signal_connect_swapped (action,
+                                  "notify::tooltip",
+                                  G_CALLBACK (on_button_action_tooltip_changed),
+                                  button);
+
+        on_button_action_label_changed (button);
+        on_button_action_icon_name_changed (button);
+        on_button_action_tooltip_changed (button);
+
+        if (strcmp (gtk_action_get_name (action),
+                    GDM_LOGIN_EXTENSION_DEFAULT_ACTION) == 0) {
+                gtk_widget_set_can_default (button, TRUE);
+        }
+
+        return button;
+}
+
+static void
+create_buttons_for_actions (GdmGreeterLoginWindow *login_window,
+                            GtkActionGroup        *actions)
+{
+        GList *action_list;
+        GList *node;
+        GtkWidget *box;
+
+        action_list = gtk_action_group_list_actions (actions);
+
+        box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "buttonbox"));
+        for (node = action_list; node != NULL; node = node->next) {
+                GtkAction *action;
+                GtkWidget *button;
+
+                action = node->data;
+
+                button = create_button_from_action (action);
+                gtk_container_add (GTK_CONTAINER (box), button);
+        }
+
+        g_list_free (action_list);
+}
+
+static void
+gdm_greeter_login_window_add_extension (GdmGreeterLoginWindow *login_window,
+                                        GdmLoginExtension     *extension)
+{
+        char *name;
+        char *description;
+        char *service_name;
+        GtkActionGroup *actions;
+
+        g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window));
+        g_return_if_fail (GDM_IS_LOGIN_EXTENSION (extension));
+
+        name = gdm_login_extension_get_name (extension);
+        description = gdm_login_extension_get_description (extension);
+
+        if (!gdm_login_extension_is_visible (extension)) {
+                g_debug ("GdmGreeterLoginWindow: new extension '%s - %s' won't be added",
+                         name, description);
+                g_free (name);
+                g_free (description);
+                return;
+        }
+
+        actions = gdm_login_extension_get_actions (extension);
+
+        create_buttons_for_actions (login_window, actions);
+        hide_extension_actions (extension);
+
+        g_object_unref (actions);
+
+        g_signal_connect_swapped (extension,
+                                  "answer",
+                                  G_CALLBACK (on_login_extension_answer),
+                                  login_window);
+        g_signal_connect_swapped (extension,
+                                  "cancel",
+                                  G_CALLBACK (on_login_extension_cancel),
+                                  login_window);
+        g_signal_connect_swapped (extension,
+                                  "user-chosen",
+                                  G_CALLBACK (on_login_extension_chose_user),
+                                  login_window);
+        g_signal_connect_swapped (extension,
+                                  "message-queue-empty",
+                                  G_CALLBACK (on_login_extension_message_queue_empty),
+                                  login_window);
+
+        g_debug ("GdmGreeterLoginWindow: new extension '%s - %s' added",
+                name, description);
+
+        login_window->priv->extensions = g_list_append (login_window->priv->extensions, extension);
+        service_name = gdm_login_extension_get_service_name (extension);
+
+        if (gdm_login_extension_is_choosable (extension)) {
+                gdm_chooser_widget_add_item (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser),
+                                             service_name, NULL, name, description, ~0,
+                                             FALSE, TRUE, NULL, NULL);
+        }
+
+        g_free (name);
+        g_free (description);
+
+        g_debug ("GdmGreeterLoginWindow: starting conversation with '%s'", service_name);
+        g_signal_emit (login_window, signals[START_CONVERSATION], 0, service_name);
+        g_free (service_name);
+}
+
+static gboolean
+on_window_state_event (GtkWidget           *widget,
+                       GdkEventWindowState *event,
+                       gpointer             data)
+{
+        if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) {
+                g_debug ("GdmGreeterLoginWindow: window iconified");
+                gtk_window_deiconify (GTK_WINDOW (widget));
+        }
+
+        return FALSE;
+}
+
+static gboolean
+load_login_extensions (GdmGreeterLoginWindow *login_window)
+{
+        GList *extensions, *node;
+        GIOExtensionPoint *extension_point;
+
+        g_debug ("GdmGreeterLoginWindow: loading extensions");
+
+        extension_point = g_io_extension_point_register (GDM_LOGIN_EXTENSION_POINT_NAME);
+        g_io_extension_point_set_required_type (extension_point,
+                                                GDM_TYPE_LOGIN_EXTENSION);
+
+        g_io_modules_load_all_in_directory (GDM_SIMPLE_GREETER_PLUGINS_DIR);
+
+        extensions = g_io_extension_point_get_extensions (extension_point);
+
+        if (extensions == NULL) {
+                gdm_unified_extension_load ();
+                extensions = g_io_extension_point_get_extensions (extension_point);
+        }
+
+        for (node = extensions; node != NULL; node = node->next) {
+                GIOExtension *extension;
+                GdmLoginExtension *login_extension;
+
+                extension = (GIOExtension *) node->data;
+
+                g_debug ("GdmGreeterLoginWindow: adding extension '%s'",
+                         g_io_extension_get_name (extension));
+
+                login_extension = g_object_new (g_io_extension_get_type (extension), NULL);
+
+                gdm_greeter_login_window_add_extension (GDM_GREETER_LOGIN_WINDOW (login_window),
+                                                        login_extension);
+        }
+
+        g_debug ("GdmGreeterLoginWindow: done loading extensions");
+
+        return FALSE;
+}
+
+static void
+gdm_greeter_login_window_init (GdmGreeterLoginWindow *login_window)
+{
+        GSettings *settings;
+        gboolean   user_list_disable;
+
+        gdm_profile_start (NULL);
+
+        login_window->priv = GDM_GREETER_LOGIN_WINDOW_GET_PRIVATE (login_window);
+        login_window->priv->timed_login_enabled = FALSE;
+        login_window->priv->dialog_mode = MODE_UNDEFINED;
+        login_window->priv->next_mode = MODE_UNDEFINED;
+
+        settings = g_settings_new (LOGIN_SCREEN_SCHEMA);
+
+        /* The user list is not shown only if the user list is disabled and
+         * timed login is also not being used.
+         */
+        user_list_disable = g_settings_get_boolean (settings, KEY_DISABLE_USER_LIST);
+
+        login_window->priv->user_list_disabled = user_list_disable;
+
+        gtk_window_set_title (GTK_WINDOW (login_window), _("Login Window"));
+        /*gtk_window_set_opacity (GTK_WINDOW (login_window), 0.85);*/
+        gtk_window_set_position (GTK_WINDOW (login_window), GTK_WIN_POS_CENTER_ALWAYS);
+        gtk_window_set_deletable (GTK_WINDOW (login_window), FALSE);
+        gtk_window_set_decorated (GTK_WINDOW (login_window), FALSE);
+        gtk_window_set_keep_below (GTK_WINDOW (login_window), TRUE);
+        gtk_window_set_skip_taskbar_hint (GTK_WINDOW (login_window), TRUE);
+        gtk_window_set_skip_pager_hint (GTK_WINDOW (login_window), TRUE);
+        gtk_window_stick (GTK_WINDOW (login_window));
+        gtk_container_set_border_width (GTK_CONTAINER (login_window), 0);
+
+        g_signal_connect (login_window,
+                          "window-state-event",
+                          G_CALLBACK (on_window_state_event),
+                          NULL);
+
+        login_window->priv->settings = g_settings_new (LOGIN_SCREEN_SCHEMA);
+
+        login_window->priv->gsettings_cnxn = g_signal_connect (login_window->priv->settings,
+                                                               "changed",
+                                                               G_CALLBACK (on_gsettings_key_changed),
+                                                               login_window);
+
+        g_idle_add ((GSourceFunc) load_login_extensions, login_window);
+        gdm_profile_end (NULL);
+}
+
+static void
+gdm_greeter_login_window_finalize (GObject *object)
+{
+        GdmGreeterLoginWindow *login_window;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (object));
+
+        login_window = GDM_GREETER_LOGIN_WINDOW (object);
+
+        g_return_if_fail (login_window->priv != NULL);
+
+        if (login_window->priv->settings != NULL) {
+                g_object_unref (login_window->priv->settings);
+        }
+
+        G_OBJECT_CLASS (gdm_greeter_login_window_parent_class)->finalize (object);
+}
+
+GtkWidget *
+gdm_greeter_login_window_new (gboolean is_local)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_GREETER_LOGIN_WINDOW,
+                               "display-is-local", is_local,
+                               "resizable", FALSE,
+                               NULL);
+
+        return GTK_WIDGET (object);
+}
diff --git a/gui/simple-greeter/gdm-greeter-login-window.h b/gui/simple-greeter/gdm-greeter-login-window.h
new file mode 100644
index 0000000..6db3e04
--- /dev/null
+++ b/gui/simple-greeter/gdm-greeter-login-window.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef __GDM_GREETER_LOGIN_WINDOW_H
+#define __GDM_GREETER_LOGIN_WINDOW_H
+
+#include <glib-object.h>
+#include "gdm-login-extension.h"
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_GREETER_LOGIN_WINDOW         (gdm_greeter_login_window_get_type ())
+#define GDM_GREETER_LOGIN_WINDOW(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
GDM_TYPE_GREETER_LOGIN_WINDOW, GdmGreeterLoginWindow))
+#define GDM_GREETER_LOGIN_WINDOW_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_GREETER_LOGIN_WINDOW, 
GdmGreeterLoginWindowClass))
+#define GDM_IS_GREETER_LOGIN_WINDOW(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
GDM_TYPE_GREETER_LOGIN_WINDOW))
+#define GDM_IS_GREETER_LOGIN_WINDOW_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_GREETER_LOGIN_WINDOW))
+#define GDM_GREETER_LOGIN_WINDOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
GDM_TYPE_GREETER_LOGIN_WINDOW, GdmGreeterLoginWindowClass))
+
+typedef struct GdmGreeterLoginWindowPrivate GdmGreeterLoginWindowPrivate;
+
+typedef struct
+{
+        GtkWindow                     parent;
+        GdmGreeterLoginWindowPrivate *priv;
+} GdmGreeterLoginWindow;
+
+typedef struct
+{
+        GtkWindowClass   parent_class;
+
+        /* signals */
+        void (* start_conversation)          (GdmGreeterLoginWindow *login_window,
+                                              const char            *service_name);
+        void (* begin_auto_login)            (GdmGreeterLoginWindow *login_window,
+                                              const char            *username);
+        void (* begin_verification)          (GdmGreeterLoginWindow *login_window,
+                                              const char            *service_name);
+        void (* begin_verification_for_user) (GdmGreeterLoginWindow *login_window,
+                                              const char            *service_name,
+                                              const char            *username);
+        void (* query_answer)                (GdmGreeterLoginWindow *login_window,
+                                              const char            *service_name,
+                                              const char            *text);
+        void (* user_selected)               (GdmGreeterLoginWindow *login_window,
+                                              const char            *text);
+        void (* session_selected)            (GdmGreeterLoginWindow *login_window,
+                                              const char            *text);
+        void (* cancelled)                   (GdmGreeterLoginWindow *login_window);
+        void (* start_session)               (GdmGreeterLoginWindow *login_window);
+
+} GdmGreeterLoginWindowClass;
+
+GType               gdm_greeter_login_window_get_type           (void);
+GtkWidget *         gdm_greeter_login_window_new                (gboolean display_is_local);
+
+
+gboolean            gdm_greeter_login_window_reset              (GdmGreeterLoginWindow *login_window);
+gboolean            gdm_greeter_login_window_ready              (GdmGreeterLoginWindow *login_window,
+                                                                 const char            *service_name);
+gboolean            gdm_greeter_login_window_conversation_stopped (GdmGreeterLoginWindow *login_window,
+                                                                   const char            *service_name);
+gboolean            gdm_greeter_login_window_info_query         (GdmGreeterLoginWindow *login_window,
+                                                                 const char *service_name,
+                                                                 const char *text);
+gboolean            gdm_greeter_login_window_secret_info_query  (GdmGreeterLoginWindow *login_window,
+                                                                 const char *service_name,
+                                                                 const char *text);
+gboolean            gdm_greeter_login_window_info               (GdmGreeterLoginWindow *login_window,
+                                                                 const char *service_name,
+                                                                 const char *text);
+gboolean            gdm_greeter_login_window_problem            (GdmGreeterLoginWindow *login_window,
+                                                                 const char *service_name,
+                                                                 const char *text);
+void                gdm_greeter_login_window_set_default_session_name (GdmGreeterLoginWindow *login_window,
+                                                                       const char *text);
+
+gboolean            gdm_greeter_login_window_service_unavailable (GdmGreeterLoginWindow *login_window,
+                                                                  const char *service_name);
+
+void               gdm_greeter_login_window_request_timed_login (GdmGreeterLoginWindow *login_window,
+                                                                 const char            *username,
+                                                                 int                    delay);
+void               gdm_greeter_login_window_session_opened      (GdmGreeterLoginWindow *login_window,
+                                                                 const char            *service_name);
+
+G_END_DECLS
+
+#endif /* __GDM_GREETER_LOGIN_WINDOW_H */
diff --git a/gui/simple-greeter/gdm-greeter-login-window.ui b/gui/simple-greeter/gdm-greeter-login-window.ui
new file mode 100644
index 0000000..163a8f4
--- /dev/null
+++ b/gui/simple-greeter/gdm-greeter-login-window.ui
@@ -0,0 +1,284 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+      <object class="GtkFrame" id="window-frame">
+        <property name="visible">True</property>
+        <property name="label_xalign">0</property>
+        <property name="shadow_type">out</property>
+        <child>
+          <object class="GtkAlignment" id="alignment2">
+            <property name="visible">True</property>
+            <property name="border_width">24</property>
+            <child>
+              <object class="GtkVBox" id="window-box">
+                <property name="visible">True</property>
+                <property name="spacing">10</property>
+                <child>
+                  <object class="GtkAlignment" id="alignment1">
+                    <property name="visible">True</property>
+                    <child>
+                      <object class="GtkImage" id="logo-image">
+                        <property name="visible">True</property>
+                        <property name="pixel_size">48</property>
+                        <property name="icon_name">computer</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkEventBox" id="computer-info-event-box">
+                    <property name="visible">True</property>
+                    <property name="visible_window">False</property>
+                    <child>
+                      <object class="GtkNotebook" id="computer-info-notebook">
+                        <property name="visible">True</property>
+                        <property name="show_tabs">False</property>
+                        <property name="show_border">False</property>
+                        <child>
+                          <object class="GtkLabel" id="computer-info-name-label">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">Computer Name</property>
+                          </object>
+                        </child>
+                        <child type="tab">
+                          <object class="GtkLabel" id="label3">
+                            <property name="visible">True</property>
+                            <property name="label">page 5</property>
+                          </object>
+                          <packing>
+                            <property name="tab_fill">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="computer-info-version-label">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">Version</property>
+                          </object>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child type="tab">
+                          <object class="GtkLabel" id="label11">
+                            <property name="visible">True</property>
+                          </object>
+                          <packing>
+                            <property name="position">1</property>
+                            <property name="tab_fill">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child type="tab">
+                          <object class="GtkLabel" id="label12">
+                            <property name="visible">True</property>
+                          </object>
+                          <packing>
+                            <property name="position">2</property>
+                            <property name="tab_fill">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child type="tab">
+                          <object class="GtkLabel" id="label13">
+                            <property name="visible">True</property>
+                          </object>
+                          <packing>
+                            <property name="position">3</property>
+                            <property name="tab_fill">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child type="tab">
+                          <object class="GtkLabel" id="label14">
+                            <property name="visible">True</property>
+                          </object>
+                          <packing>
+                            <property name="position">4</property>
+                            <property name="tab_fill">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child type="tab">
+                          <object class="GtkLabel" id="label15">
+                            <property name="visible">True</property>
+                          </object>
+                          <packing>
+                            <property name="position">5</property>
+                            <property name="tab_fill">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child type="tab">
+                          <object class="GtkLabel" id="label16">
+                            <property name="visible">True</property>
+                          </object>
+                          <packing>
+                            <property name="position">6</property>
+                            <property name="tab_fill">False</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="auth-banner-label">
+                    <property name="visible">True</property>
+                    <property name="justify">center</property>
+                    <property name="wrap">True</property>
+                  </object>
+                  <packing>
+                    <property name="position">3</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkAlignment" id="alignment3">
+                    <property name="visible">True</property>
+                    <child>
+                      <object class="GtkVBox" id="selection-box">
+                        <property name="visible">True</property>
+                        <property name="spacing">2</property>
+                        <child>
+                          <object class="GtkAlignment" id="task-list-alignment">
+                            <property name="visible">True</property>
+                            <property name="xalign">1.0</property>
+                            <property name="xscale">0.0</property>
+                            <child>
+                              <object class="GdmExtensionList" id="extension-list">
+                                <property name="visible">False</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GdmUserChooserWidget" id="user-chooser">
+                            <property name="visible">False</property>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkHBox" id="auth-page-box">
+                            <property name="visible">True</property>
+                            <property name="border_width">10</property>
+                            <child>
+                              <placeholder/>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">4</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkHButtonBox" id="buttonbox">
+                    <property name="visible">True</property>
+                    <property name="spacing">6</property>
+                    <property name="layout_style">end</property>
+                    <child>
+                      <object class="GdmSessionOptionWidget" id="session-option-widget">
+                        <property name="visible">False</property>
+                        <property name="xscale">0.0</property>
+                        <property name="yscale">0.0</property>
+                        <property name="xalign">0.0</property>
+                        <property name="yalign">1.0</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                        <property name="secondary">True</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="cancel-button">
+                        <property name="label" translatable="yes">Cancel</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="unlock-button">
+                        <property name="label" translatable="yes">Unlock</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="can_default">True</property>
+                        <property name="receives_default">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="log-in-button">
+                        <property name="label" translatable="yes">Login</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="can_default">True</property>
+                        <property name="has_default">False</property>
+                        <property name="receives_default">False</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">5</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="pack_type">end</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+</interface>
diff --git a/gui/simple-greeter/gdm-greeter-panel.c b/gui/simple-greeter/gdm-greeter-panel.c
new file mode 100644
index 0000000..ce00b90
--- /dev/null
+++ b/gui/simple-greeter/gdm-greeter-panel.c
@@ -0,0 +1,1207 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
+ * Copyright (C) 2011 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <string.h>
+
+#ifdef ENABLE_RBAC_SHUTDOWN
+#include <auth_attr.h>
+#include <secdb.h>
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#ifdef HAVE_UPOWER
+#include <upower.h>
+#endif
+
+#include "gdm-greeter-panel.h"
+#include "gdm-clock-widget.h"
+#include "gdm-timer.h"
+#include "gdm-profile.h"
+#include "gdm-common.h"
+
+#define CK_NAME              "org.freedesktop.ConsoleKit"
+#define CK_MANAGER_PATH      "/org/freedesktop/ConsoleKit/Manager"
+#define CK_MANAGER_INTERFACE "org.freedesktop.ConsoleKit.Manager"
+
+#define LOGIN1_NAME      "org.freedesktop.login1"
+#define LOGIN1_PATH      "/org/freedesktop/login1"
+#define LOGIN1_INTERFACE "org.freedesktop.login1.Manager"
+
+#define GPM_DBUS_NAME      "org.gnome.SettingsDaemon"
+#define GPM_DBUS_PATH      "/org/gnome/SettingsDaemon/Power"
+#define GPM_DBUS_INTERFACE "org.gnome.SettingsDaemon.Power"
+
+#define LOGIN_SCREEN_SCHEMA           "org.gnome.login-screen"
+
+#define KEY_DISABLE_RESTART_BUTTONS   "disable-restart-buttons"
+
+#define KEY_NOTIFICATION_AREA_PADDING "/apps/notification_area_applet/prefs/padding"
+
+#define GDM_GREETER_PANEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_GREETER_PANEL, 
GdmGreeterPanelPrivate))
+
+struct GdmGreeterPanelPrivate
+{
+        int                     monitor;
+        GdkRectangle            geometry;
+        GtkWidget              *hbox;
+        GtkWidget              *left_hbox;
+        GtkWidget              *right_hbox;
+        GtkWidget              *alignment;
+        GtkWidget              *hostname_label;
+        GtkWidget              *clock;
+        GtkWidget              *status_menubar;
+        GtkWidget              *shutdown_menu;
+
+        GdmTimer               *animation_timer;
+        double                  progress;
+
+        GtkWidget              *power_image;
+        GtkWidget              *power_menu_item;
+        GtkWidget              *power_menubar_item;
+        GDBusProxy             *power_proxy;
+        gulong                  power_proxy_signal_handler;
+        gulong                  power_proxy_properties_changed_handler;
+
+        guint                   display_is_local : 1;
+};
+
+enum {
+        PROP_0,
+        PROP_MONITOR,
+        PROP_DISPLAY_IS_LOCAL
+};
+
+enum {
+        DISCONNECTED,
+        NUMBER_OF_SIGNALS
+};
+
+static guint signals [NUMBER_OF_SIGNALS] = { 0, };
+
+static void     gdm_greeter_panel_class_init  (GdmGreeterPanelClass *klass);
+static void     gdm_greeter_panel_init        (GdmGreeterPanel      *greeter_panel);
+static void     gdm_greeter_panel_finalize    (GObject              *object);
+
+G_DEFINE_TYPE (GdmGreeterPanel, gdm_greeter_panel, GTK_TYPE_WINDOW)
+
+static void
+gdm_greeter_panel_set_monitor (GdmGreeterPanel *panel,
+                               int              monitor)
+{
+        g_return_if_fail (GDM_IS_GREETER_PANEL (panel));
+
+        if (panel->priv->monitor == monitor) {
+                return;
+        }
+
+        panel->priv->monitor = monitor;
+
+        gtk_widget_queue_resize (GTK_WIDGET (panel));
+
+        g_object_notify (G_OBJECT (panel), "monitor");
+}
+
+static void
+_gdm_greeter_panel_set_display_is_local (GdmGreeterPanel *panel,
+                                         gboolean         is)
+{
+        if (panel->priv->display_is_local != is) {
+                panel->priv->display_is_local = is;
+                g_object_notify (G_OBJECT (panel), "display-is-local");
+        }
+}
+
+static void
+gdm_greeter_panel_set_property (GObject        *object,
+                                guint           prop_id,
+                                const GValue   *value,
+                                GParamSpec     *pspec)
+{
+        GdmGreeterPanel *self;
+
+        self = GDM_GREETER_PANEL (object);
+
+        switch (prop_id) {
+        case PROP_MONITOR:
+                gdm_greeter_panel_set_monitor (self, g_value_get_int (value));
+                break;
+        case PROP_DISPLAY_IS_LOCAL:
+                _gdm_greeter_panel_set_display_is_local (self, g_value_get_boolean (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gdm_greeter_panel_get_property (GObject        *object,
+                                guint           prop_id,
+                                GValue         *value,
+                                GParamSpec     *pspec)
+{
+        GdmGreeterPanel *self;
+
+        self = GDM_GREETER_PANEL (object);
+
+        switch (prop_id) {
+        case PROP_MONITOR:
+                g_value_set_int (value, self->priv->monitor);
+                break;
+        case PROP_DISPLAY_IS_LOCAL:
+                g_value_set_boolean (value, self->priv->display_is_local);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gdm_greeter_panel_dispose (GObject *object)
+{
+        GdmGreeterPanel *panel;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_GREETER_PANEL (object));
+
+        panel = GDM_GREETER_PANEL (object);
+
+        if (panel->priv->power_proxy != NULL) {
+                g_object_unref (panel->priv->power_proxy);
+                panel->priv->power_proxy = NULL;
+        }
+
+        G_OBJECT_CLASS (gdm_greeter_panel_parent_class)->dispose (object);
+}
+
+/* copied from panel-toplevel.c */
+static void
+gdm_greeter_panel_move_resize_window (GdmGreeterPanel *panel,
+                                      gboolean         move,
+                                      gboolean         resize)
+{
+        GtkWidget *widget;
+
+        widget = GTK_WIDGET (panel);
+
+        g_assert (gtk_widget_get_realized (widget));
+
+        if (move && resize) {
+                gdk_window_move_resize (gtk_widget_get_window (widget),
+                                        panel->priv->geometry.x,
+                                        panel->priv->geometry.y,
+                                        panel->priv->geometry.width,
+                                        panel->priv->geometry.height);
+        } else if (move) {
+                gdk_window_move (gtk_widget_get_window (widget),
+                                 panel->priv->geometry.x,
+                                 panel->priv->geometry.y);
+        } else if (resize) {
+                gdk_window_resize (gtk_widget_get_window (widget),
+                                   panel->priv->geometry.width,
+                                   panel->priv->geometry.height);
+        }
+}
+
+static void
+on_screen_size_changed (GdkScreen       *screen,
+                        GdmGreeterPanel *panel)
+{
+        gtk_widget_queue_resize (GTK_WIDGET (panel));
+}
+
+static void
+update_power_icon (GdmGreeterPanel *panel)
+{
+        GVariant *variant;
+
+        g_assert (panel->priv->power_proxy != NULL);
+
+        variant = g_dbus_proxy_get_cached_property (panel->priv->power_proxy, "Icon");
+        if (variant == NULL) {
+                /* FIXME: use an indeterminant icon */
+                return;
+        }
+
+        if (g_variant_is_of_type (variant, G_VARIANT_TYPE ("s"))) {
+                const char *name;
+
+                name = g_variant_get_string (variant, NULL);
+
+                if (name != NULL && *name != '\0') {
+                        GError *error;
+                        GIcon  *icon;
+                        error = NULL;
+                        icon = g_icon_new_for_string (name, &error);
+                        if (icon != NULL) {
+                                g_debug ("setting power icon %s", name);
+                                gtk_image_set_from_gicon (GTK_IMAGE (panel->priv->power_image),
+                                                          icon,
+                                                          GTK_ICON_SIZE_MENU);
+                                gtk_widget_show_all (panel->priv->power_menubar_item);
+                        } else {
+                                gtk_widget_hide (panel->priv->power_menubar_item);
+                        }
+                } else {
+                        gtk_widget_hide (panel->priv->power_menubar_item);
+                }
+        }
+
+        g_variant_unref (variant);
+}
+
+static void
+update_power_menu (GdmGreeterPanel *panel)
+{
+        GVariant *variant;
+
+        g_assert (panel->priv->power_proxy != NULL);
+
+        variant = g_dbus_proxy_get_cached_property (panel->priv->power_proxy, "Tooltip");
+        if (variant == NULL) {
+                /* FIXME: use an indeterminant message */
+                return;
+        }
+
+        if (g_variant_is_of_type (variant, G_VARIANT_TYPE ("s"))) {
+                const char *txt;
+
+                txt = g_variant_get_string (variant, NULL);
+                if (txt != NULL) {
+                        gtk_menu_item_set_label (GTK_MENU_ITEM (panel->priv->power_menu_item), txt);
+                }
+        }
+
+        g_variant_unref (variant);
+}
+
+static void
+on_power_proxy_g_signal (GDBusProxy      *proxy,
+                         const char      *sender_name,
+                         const char      *signal_name,
+                         GVariant        *parameters,
+                         GdmGreeterPanel *panel)
+{
+        if (g_strcmp0 (signal_name, "Changed") == 0) {
+                //update_power_icon (panel);
+        }
+}
+
+static void
+on_power_proxy_g_properties_changed (GDBusProxy      *proxy,
+                                     GVariant        *changed_properties,
+                                     GStrv           *invalidated_properties,
+                                     GdmGreeterPanel *panel)
+{
+        g_debug ("Got power properties changed");
+        if (g_variant_n_children (changed_properties) > 0) {
+                GVariantIter iter;
+                GVariant    *value;
+                char        *key;
+
+                g_variant_iter_init (&iter, changed_properties);
+
+                while (g_variant_iter_loop (&iter, "{&sv}", &key, &value)) {
+                        if (g_strcmp0 (key, "Icon") == 0) {
+                                g_debug ("Got power Icon changed");
+                                update_power_icon (panel);
+                        } else if (g_strcmp0 (key, "Tooltip") == 0) {
+                                g_debug ("Got power tooltip changed");
+                                update_power_menu (panel);
+                        }
+                }
+        }
+}
+
+static void
+gdm_greeter_panel_real_realize (GtkWidget *widget)
+{
+        GdmGreeterPanel *panel = GDM_GREETER_PANEL (widget);
+
+        if (GTK_WIDGET_CLASS (gdm_greeter_panel_parent_class)->realize) {
+                GTK_WIDGET_CLASS (gdm_greeter_panel_parent_class)->realize (widget);
+        }
+
+        gdk_window_set_geometry_hints (gtk_widget_get_window (widget), NULL, GDK_HINT_POS);
+
+        gdm_greeter_panel_move_resize_window (GDM_GREETER_PANEL (widget), TRUE, TRUE);
+
+        g_signal_connect (gtk_window_get_screen (GTK_WINDOW (widget)),
+                          "size_changed",
+                          G_CALLBACK (on_screen_size_changed),
+                          widget);
+
+        if (panel->priv->power_proxy != NULL) {
+                update_power_icon (panel);
+                update_power_menu (panel);
+                panel->priv->power_proxy_signal_handler = g_signal_connect (panel->priv->power_proxy,
+                                                                            "g-signal",
+                                                                            G_CALLBACK 
(on_power_proxy_g_signal),
+                                                                            panel);
+        panel->priv->power_proxy_properties_changed_handler = g_signal_connect (panel->priv->power_proxy,
+                                                                                "g-properties-changed",
+                                                                                G_CALLBACK 
(on_power_proxy_g_properties_changed),
+                                                                                panel);
+        }
+
+}
+
+static void
+gdm_greeter_panel_real_unrealize (GtkWidget *widget)
+{
+        GdmGreeterPanel *panel = GDM_GREETER_PANEL (widget);
+
+        g_signal_handlers_disconnect_by_func (gtk_window_get_screen (GTK_WINDOW (widget)),
+                                              on_screen_size_changed,
+                                              widget);
+
+        if (panel->priv->power_proxy != NULL
+            && panel->priv->power_proxy_signal_handler != 0) {
+                g_signal_handler_disconnect (panel->priv->power_proxy, 
panel->priv->power_proxy_signal_handler);
+                g_signal_handler_disconnect (panel->priv->power_proxy, 
panel->priv->power_proxy_properties_changed_handler);
+        }
+
+        if (GTK_WIDGET_CLASS (gdm_greeter_panel_parent_class)->unrealize) {
+                GTK_WIDGET_CLASS (gdm_greeter_panel_parent_class)->unrealize (widget);
+        }
+}
+
+static void
+set_struts (GdmGreeterPanel *panel,
+            int              x,
+            int              y,
+            int              width,
+            int              height)
+{
+        gulong        data[12] = { 0, };
+
+        /* _NET_WM_STRUT_PARTIAL: CARDINAL[12]/32
+         *
+         * 0: left          1: right       2:  top             3:  bottom
+         * 4: left_start_y  5: left_end_y  6:  right_start_y   7:  right_end_y
+         * 8: top_start_x   9: top_end_x   10: bottom_start_x  11: bottom_end_x
+         *
+         * Note: In xinerama use struts relative to combined screen dimensions,
+         *       not just the current monitor.
+         */
+
+        /* top */
+        data[2] = panel->priv->geometry.y + height;
+        /* top_start_x */
+        data[8] = x;
+        /* top_end_x */
+        data[9] = x + width;
+
+#if 0
+        g_debug ("Setting strut: top=%lu top_start_x=%lu top_end_x=%lu", data[2], data[8], data[9]);
+#endif
+
+        gdk_error_trap_push ();
+        if (gtk_widget_get_window (GTK_WIDGET (panel)) != NULL) {
+                gdk_property_change (gtk_widget_get_window (GTK_WIDGET (panel)),
+                                     gdk_atom_intern ("_NET_WM_STRUT_PARTIAL", FALSE),
+                                     gdk_atom_intern ("CARDINAL", FALSE),
+                                     32,
+                                     GDK_PROP_MODE_REPLACE,
+                                     (guchar *) &data,
+                                     12);
+
+                gdk_property_change (gtk_widget_get_window (GTK_WIDGET (panel)),
+                                     gdk_atom_intern ("_NET_WM_STRUT", FALSE),
+                                     gdk_atom_intern ("CARDINAL", FALSE),
+                                     32,
+                                     GDK_PROP_MODE_REPLACE,
+                                     (guchar *) &data,
+                                     4);
+        }
+
+        gdk_error_trap_pop_ignored ();
+}
+
+static void
+update_struts (GdmGreeterPanel *panel)
+{
+        /* FIXME: assumes only one panel */
+        set_struts (panel,
+                    panel->priv->geometry.x,
+                    panel->priv->geometry.y,
+                    panel->priv->geometry.width,
+                    panel->priv->geometry.height);
+}
+
+static void
+update_geometry (GdmGreeterPanel *panel,
+                 GtkRequisition  *requisition)
+{
+        GdkRectangle geometry;
+
+        gdk_screen_get_monitor_geometry (gtk_window_get_screen (GTK_WINDOW (panel)),
+                                         panel->priv->monitor,
+                                         &geometry);
+
+        panel->priv->geometry.width = geometry.width;
+        panel->priv->geometry.height = requisition->height + 2 * gtk_container_get_border_width 
(GTK_CONTAINER (panel));
+
+        panel->priv->geometry.x = geometry.x;
+        panel->priv->geometry.y = geometry.y - panel->priv->geometry.height + panel->priv->progress * 
panel->priv->geometry.height;
+
+#if 0
+        panel->priv->geometry.y += 50;
+#endif
+#if 0
+        g_debug ("Setting geometry x:%d y:%d w:%d h:%d",
+                 panel->priv->geometry.x,
+                 panel->priv->geometry.y,
+                 panel->priv->geometry.width,
+                 panel->priv->geometry.height);
+#endif
+
+        update_struts (panel);
+}
+
+static void
+gdm_greeter_panel_get_preferred_size (GtkWidget      *widget,
+                                      GtkOrientation  orientation,
+                                      gint           *minimum_size,
+                                      gint           *natural_size)
+{
+        GdmGreeterPanel *panel;
+        GtkBin          *bin;
+        GtkWidget       *child;
+        GdkRectangle     old_geometry;
+        int              position_changed = FALSE;
+        int              size_changed = FALSE;
+        GtkRequisition   minimum_req, natural_req;
+
+        panel = GDM_GREETER_PANEL (widget);
+        bin = GTK_BIN (widget);
+        child = gtk_bin_get_child (bin);
+
+        minimum_req.width = 0;
+        minimum_req.height = 0;
+        natural_req.width = minimum_req.width;
+        natural_req.height = minimum_req.height;
+
+        if (child != NULL && gtk_widget_get_visible (child)) {
+                int min_child_width, nat_child_width;
+                int min_child_height, nat_child_height;
+
+                gtk_widget_get_preferred_width (gtk_bin_get_child (bin),
+                                                &min_child_width,
+                                                &nat_child_width);
+                gtk_widget_get_preferred_height (gtk_bin_get_child (bin),
+                                                 &min_child_height,
+                                                 &nat_child_height);
+
+                minimum_req.width += min_child_width;
+                natural_req.width += nat_child_width;
+                minimum_req.height += min_child_height;
+                natural_req.height += nat_child_height;
+        }
+
+        old_geometry = panel->priv->geometry;
+        update_geometry (panel, &natural_req);
+
+        if (!gtk_widget_get_realized (widget))
+                goto out;
+
+        if (old_geometry.width  != panel->priv->geometry.width ||
+            old_geometry.height != panel->priv->geometry.height) {
+                size_changed = TRUE;
+        }
+
+        if (old_geometry.x != panel->priv->geometry.x ||
+            old_geometry.y != panel->priv->geometry.y) {
+                position_changed = TRUE;
+        }
+
+        gdm_greeter_panel_move_resize_window (panel, position_changed, size_changed);
+
+ out:
+
+        if (orientation == GTK_ORIENTATION_HORIZONTAL) {
+                if (minimum_size)
+                        *minimum_size = panel->priv->geometry.width;
+                if (natural_size)
+                        *natural_size = panel->priv->geometry.width;
+        } else {
+                if (minimum_size)
+                        *minimum_size = panel->priv->geometry.height;
+                if (natural_size)
+                        *natural_size = panel->priv->geometry.height;
+        }
+}
+
+static void
+gdm_greeter_panel_real_get_preferred_width (GtkWidget *widget,
+                                            gint      *minimum_size,
+                                            gint      *natural_size)
+{
+        gdm_greeter_panel_get_preferred_size (widget, GTK_ORIENTATION_HORIZONTAL, minimum_size, 
natural_size);
+}
+
+static void
+gdm_greeter_panel_real_get_preferred_height (GtkWidget *widget,
+                                             gint      *minimum_size,
+                                             gint      *natural_size)
+{
+        gdm_greeter_panel_get_preferred_size (widget, GTK_ORIENTATION_VERTICAL, minimum_size, natural_size);
+}
+
+static void
+gdm_greeter_panel_real_show (GtkWidget *widget)
+{
+        GdmGreeterPanel *panel;
+        GtkSettings *settings;
+        gboolean     animations_are_enabled;
+
+        settings = gtk_settings_get_for_screen (gtk_widget_get_screen (widget));
+        g_object_get (settings, "gtk-enable-animations", &animations_are_enabled, NULL);
+
+        panel = GDM_GREETER_PANEL (widget);
+
+        if (animations_are_enabled) {
+                gdm_timer_start (panel->priv->animation_timer, 1.0);
+        } else {
+                panel->priv->progress = 1.0;
+        }
+
+        GTK_WIDGET_CLASS (gdm_greeter_panel_parent_class)->show (widget);
+}
+
+static void
+gdm_greeter_panel_real_hide (GtkWidget *widget)
+{
+        GdmGreeterPanel *panel;
+
+        panel = GDM_GREETER_PANEL (widget);
+
+        gdm_timer_stop (panel->priv->animation_timer);
+        panel->priv->progress = 0.0;
+
+        GTK_WIDGET_CLASS (gdm_greeter_panel_parent_class)->hide (widget);
+}
+
+static void
+on_animation_tick (GdmGreeterPanel *panel,
+                   double           progress)
+{
+        panel->priv->progress = progress * log ((G_E - 1.0) * progress + 1.0);
+
+        gtk_widget_queue_resize (GTK_WIDGET (panel));
+}
+
+static gboolean
+try_system_stop (GDBusConnection *connection,
+                 GError         **error)
+{
+        GVariant  *reply;
+        gboolean   res;
+        GError    *call_error;
+
+        g_debug ("GdmGreeterPanel: trying to stop system");
+
+        call_error = NULL;
+        reply = g_dbus_connection_call_sync (connection,
+                                             LOGIN1_NAME,
+                                             LOGIN1_PATH,
+                                             LOGIN1_INTERFACE,
+                                             "PowerOff",
+                                             g_variant_new ("(b)", TRUE),
+                                             NULL,
+                                             G_DBUS_CALL_FLAGS_NONE,
+                                             INT_MAX,
+                                             NULL,
+                                             &call_error);
+
+        if (reply == NULL && (g_error_matches (call_error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER) ||
+                              g_error_matches (call_error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))) {
+                g_clear_error (&call_error);
+                reply = g_dbus_connection_call_sync (connection,
+                                                     CK_NAME,
+                                                     CK_MANAGER_PATH,
+                                                     CK_MANAGER_INTERFACE,
+                                                     "Stop",
+                                                     NULL,
+                                                     NULL,
+                                                     G_DBUS_CALL_FLAGS_NONE,
+                                                     INT_MAX,
+                                                     NULL,
+                                                     &call_error);
+        }
+
+        if (reply != NULL) {
+                res = TRUE;
+                g_variant_unref (reply);
+        } else {
+                g_propagate_error (error, call_error);
+                res = FALSE;
+        }
+
+        return res;
+}
+
+static gboolean
+try_system_restart (GDBusConnection *connection,
+                    GError         **error)
+{
+        GVariant  *reply;
+        gboolean   res;
+        GError    *call_error;
+
+        g_debug ("GdmGreeterPanel: trying to restart system");
+
+        call_error = NULL;
+        reply = g_dbus_connection_call_sync (connection,
+                                             LOGIN1_NAME,
+                                             LOGIN1_PATH,
+                                             LOGIN1_INTERFACE,
+                                             "Reboot",
+                                             g_variant_new ("(b)", TRUE),
+                                             NULL,
+                                             G_DBUS_CALL_FLAGS_NONE,
+                                             INT_MAX,
+                                             NULL,
+                                             &call_error);
+
+        if (reply == NULL && (g_error_matches (call_error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER)||
+                              g_error_matches (call_error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))) {
+                g_clear_error (&call_error);
+                reply = g_dbus_connection_call_sync (connection,
+                                                     CK_NAME,
+                                                     CK_MANAGER_PATH,
+                                                     CK_MANAGER_INTERFACE,
+                                                     "Restart",
+                                                     NULL,
+                                                     NULL,
+                                                     G_DBUS_CALL_FLAGS_NONE,
+                                                     INT_MAX,
+                                                     NULL,
+                                                     &call_error);
+        }
+
+        if (reply != NULL) {
+                res = TRUE;
+                g_variant_unref (reply);
+        } else {
+                g_propagate_error (error, call_error);
+                res = FALSE;
+        }
+
+        return res;
+}
+
+static gboolean
+can_suspend (void)
+{
+        gboolean ret = FALSE;
+
+#ifdef HAVE_UPOWER
+        UpClient *up_client;
+
+        /* use UPower to get data */
+        up_client = up_client_new ();
+       ret = up_client_get_can_suspend (up_client);
+        g_object_unref (up_client);
+#endif
+
+        return ret;
+}
+
+static void
+do_system_suspend (void)
+{
+#ifdef HAVE_UPOWER
+        gboolean ret;
+        UpClient *up_client;
+        GError *error = NULL;
+
+        /* use UPower to trigger suspend */
+        up_client = up_client_new ();
+        ret = up_client_suspend_sync (up_client, NULL, &error);
+        if (!ret) {
+                g_warning ("Couldn't suspend: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+        g_object_unref (up_client);
+#endif
+}
+
+static void
+do_system_restart (void)
+{
+        gboolean         res;
+        GError          *error;
+        GDBusConnection *connection;
+
+        error = NULL;
+        connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+        if (connection == NULL) {
+                g_warning ("Unable to get system bus connection: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        res = try_system_restart (connection, &error);
+        if (!res) {
+                g_debug ("GdmGreeterPanel: unable to restart system: %s",
+                         error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+do_system_stop (void)
+{
+        gboolean         res;
+        GError          *error;
+        GDBusConnection *connection;
+
+        error = NULL;
+        connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+        if (connection == NULL) {
+                g_warning ("Unable to get system bus connection: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        res = try_system_stop (connection, &error);
+        if (!res) {
+                g_debug ("GdmGreeterPanel: unable to stop system: %s",
+                         error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+do_disconnect (GtkWidget       *widget,
+               GdmGreeterPanel *panel)
+{
+        g_signal_emit (panel, signals[DISCONNECTED], 0);
+}
+
+static gboolean
+get_show_restart_buttons (GdmGreeterPanel *panel)
+{
+        gboolean   show;
+        GSettings *settings;
+
+        settings = g_settings_new (LOGIN_SCREEN_SCHEMA);
+
+        show = ! g_settings_get_boolean (settings, KEY_DISABLE_RESTART_BUTTONS);
+
+#ifdef ENABLE_RBAC_SHUTDOWN
+        {
+                char *username;
+
+                username = g_get_user_name ();
+                if (username == NULL || !chkauthattr (RBAC_SHUTDOWN_KEY, username)) {
+                        show = FALSE;
+                        g_debug ("GdmGreeterPanel: Not showing stop/restart buttons for user %s due to RBAC 
key %s",
+                                 username, RBAC_SHUTDOWN_KEY);
+                } else {
+                        g_debug ("GdmGreeterPanel: Showing stop/restart buttons for user %s due to RBAC key 
%s",
+                                 username, RBAC_SHUTDOWN_KEY);
+                }
+        }
+#endif
+        g_object_unref (settings);
+
+        return show;
+}
+
+static inline void
+override_style (GtkWidget *widget)
+{
+        GtkCssProvider  *provider;
+        GtkStyleContext *context;
+        GError          *error;
+
+        g_debug ("updating style");
+
+        context = gtk_widget_get_style_context (widget);
+
+        provider = gtk_css_provider_new ();
+        gtk_style_context_add_provider (context,
+                                        GTK_STYLE_PROVIDER (provider),
+                                        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+        error = NULL;
+        gtk_css_provider_load_from_data (provider,
+                                         "* {\n"
+                                         "  background-color: black;\n"
+                                         "  color: #ccc;\n"
+                                         "  border-width: 0;\n"
+                                         "}\n"
+                                         "*:selected {\n"
+                                         "  background-color: #666666;\n"
+                                         "  color: white;\n"
+                                         "}\n"
+                                         ".menu,\n"
+                                         ".menubar,\n"
+                                         ".menu.check,\n"
+                                         ".menu.radio {\n"
+                                         "  background-color: black;\n"
+                                         "  color: #ccc;\n"
+                                         "  border-style: none;\n"
+                                         "}\n"
+                                         ".menu:hover,\n"
+                                         ".menubar:hover,\n"
+                                         ".menu.check:hover,\n"
+                                         ".menu.radio:hover {\n"
+                                         "  background-color: #666666;\n"
+                                         "  color: #ccc;\n"
+                                         "  border-style: none;\n"
+                                         "}\n"
+                                         "GtkLabel:selected {\n"
+                                         "  background-color: black;\n"
+                                         "  color: #ccc;\n"
+                                         "}\n"
+                                         "\n"
+                                         "GtkLabel:selected:focused {\n"
+                                         "  background-color: black;\n"
+                                         "  color: #ccc;\n"
+                                         "}\n"
+                                         "GtkMenuBar {\n"
+                                         "  background-color: black;\n"
+                                         "  background-image: none;\n"
+                                         "  color: #ccc;\n"
+                                         "  -GtkMenuBar-internal-padding: 0;\n"
+                                         "  -GtkMenuBar-shadow-type: none;\n"
+                                         "  border-width: 0;\n"
+                                         "  border-style: none;\n"
+                                         "}\n"
+                                         "GtkMenuItem {\n"
+                                         "  background-color: black;\n"
+                                         "  color: #ccc;\n"
+                                         "}\n"
+                                         "GtkImage {\n"
+                                         "  background-color: black;\n"
+                                         "  color: #ccc;\n"
+                                         "}\n",
+                                         -1,
+                                         &error);
+        if (error != NULL) {
+                g_warning ("Error loading style data: %s", error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+add_shutdown_menu (GdmGreeterPanel *panel)
+{
+        GtkWidget *item;
+        GtkWidget *menu_item;
+        GtkWidget *box;
+        GtkWidget *image;
+        GIcon     *gicon;
+
+        item = gtk_menu_item_new ();
+        override_style (item);
+        box = gtk_hbox_new (FALSE, 0);
+        gtk_container_add (GTK_CONTAINER (item), box);
+        gtk_menu_shell_append (GTK_MENU_SHELL (panel->priv->status_menubar), item);
+        image = gtk_image_new ();
+        override_style (image);
+
+        gicon = g_themed_icon_new ("system-shutdown-symbolic");
+        gtk_image_set_from_gicon (GTK_IMAGE (image), gicon, GTK_ICON_SIZE_MENU);
+        g_object_unref (gicon);
+
+        gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0);
+
+        panel->priv->shutdown_menu = gtk_menu_new ();
+        gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), panel->priv->shutdown_menu);
+
+        if (! panel->priv->display_is_local) {
+                menu_item = gtk_menu_item_new_with_label ("Disconnect");
+                g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (do_disconnect), panel);
+                gtk_menu_shell_append (GTK_MENU_SHELL (panel->priv->shutdown_menu), menu_item);
+        } else if (get_show_restart_buttons (panel)) {
+                if (can_suspend ()) {
+                        menu_item = gtk_menu_item_new_with_label (_("Suspend"));
+                        g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (do_system_suspend), 
NULL);
+                        gtk_menu_shell_append (GTK_MENU_SHELL (panel->priv->shutdown_menu), menu_item);
+                }
+
+                menu_item = gtk_menu_item_new_with_label (_("Restart"));
+                g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (do_system_restart), NULL);
+                gtk_menu_shell_append (GTK_MENU_SHELL (panel->priv->shutdown_menu), menu_item);
+
+                menu_item = gtk_menu_item_new_with_label (_("Shut Down"));
+                g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (do_system_stop), NULL);
+                gtk_menu_shell_append (GTK_MENU_SHELL (panel->priv->shutdown_menu), menu_item);
+        }
+        gtk_widget_show_all (item);
+}
+
+static void
+add_battery_menu (GdmGreeterPanel *panel)
+{
+        GtkWidget *item;
+        GtkWidget *box;
+        GtkWidget *menu;
+        GError    *error;
+        GIcon     *gicon;
+
+        error = NULL;
+        panel->priv->power_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+                                                                  G_DBUS_PROXY_FLAGS_NONE,
+                                                                  NULL,
+                                                                  GPM_DBUS_NAME,
+                                                                  GPM_DBUS_PATH,
+                                                                  GPM_DBUS_INTERFACE,
+                                                                  NULL,
+                                                                  &error);
+        if (panel->priv->power_proxy == NULL) {
+                g_warning ("Unable to connect to power manager: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        item = gtk_menu_item_new ();
+
+        override_style (item);
+        box = gtk_hbox_new (FALSE, 0);
+        gtk_container_add (GTK_CONTAINER (item), box);
+        gtk_menu_shell_prepend (GTK_MENU_SHELL (panel->priv->status_menubar), item);
+        panel->priv->power_image = gtk_image_new ();
+        override_style (panel->priv->power_image);
+
+        gicon = g_themed_icon_new ("battery-caution-symbolic");
+        gtk_image_set_from_gicon (GTK_IMAGE (panel->priv->power_image), gicon, GTK_ICON_SIZE_MENU);
+        g_object_unref (gicon);
+
+        gtk_box_pack_start (GTK_BOX (box), panel->priv->power_image, FALSE, FALSE, 0);
+
+        menu = gtk_menu_new ();
+        gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
+
+        panel->priv->power_menu_item = gtk_menu_item_new_with_label (_("Unknown time remaining"));
+        gtk_widget_set_sensitive (panel->priv->power_menu_item, FALSE);
+        gtk_menu_shell_append (GTK_MENU_SHELL (menu), panel->priv->power_menu_item);
+        panel->priv->power_menubar_item = item;
+}
+
+static void
+setup_panel (GdmGreeterPanel *panel)
+{
+        GtkSizeGroup *sg;
+
+        gdm_profile_start (NULL);
+
+        gtk_widget_set_can_focus (GTK_WIDGET (panel), TRUE);
+
+        override_style (GTK_WIDGET (panel));
+
+        panel->priv->geometry.x      = -1;
+        panel->priv->geometry.y      = -1;
+        panel->priv->geometry.width  = -1;
+        panel->priv->geometry.height = -1;
+
+        gtk_window_set_title (GTK_WINDOW (panel), _("Panel"));
+        gtk_window_set_decorated (GTK_WINDOW (panel), FALSE);
+        gtk_window_set_has_resize_grip (GTK_WINDOW (panel), FALSE);
+
+        gtk_window_set_keep_above (GTK_WINDOW (panel), TRUE);
+        gtk_window_set_type_hint (GTK_WINDOW (panel), GDK_WINDOW_TYPE_HINT_DOCK);
+
+        panel->priv->hbox = gtk_hbox_new (FALSE, 12);
+        gtk_container_set_border_width (GTK_CONTAINER (panel->priv->hbox), 0);
+        gtk_widget_show (panel->priv->hbox);
+        gtk_container_add (GTK_CONTAINER (panel), panel->priv->hbox);
+
+        panel->priv->left_hbox = gtk_hbox_new (FALSE, 12);
+        gtk_container_set_border_width (GTK_CONTAINER (panel->priv->left_hbox), 0);
+        gtk_widget_show (panel->priv->left_hbox);
+        gtk_box_pack_start (GTK_BOX (panel->priv->hbox), panel->priv->left_hbox, TRUE, TRUE, 0);
+
+        panel->priv->alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+        gtk_box_pack_start (GTK_BOX (panel->priv->hbox), panel->priv->alignment, FALSE, FALSE, 0);
+        gtk_widget_show (panel->priv->alignment);
+
+        panel->priv->right_hbox = gtk_hbox_new (FALSE, 12);
+        gtk_container_set_border_width (GTK_CONTAINER (panel->priv->right_hbox), 0);
+        gtk_widget_show (panel->priv->right_hbox);
+        gtk_box_pack_start (GTK_BOX (panel->priv->hbox), panel->priv->right_hbox, TRUE, TRUE, 0);
+
+        panel->priv->clock = gdm_clock_widget_new ();
+        gtk_widget_show (panel->priv->clock);
+        gtk_container_add (GTK_CONTAINER (panel->priv->alignment), panel->priv->clock);
+
+        sg = gtk_size_group_new (GTK_SIZE_GROUP_BOTH);
+        gtk_size_group_add_widget (sg, panel->priv->left_hbox);
+        gtk_size_group_add_widget (sg, panel->priv->right_hbox);
+
+        panel->priv->status_menubar = gtk_menu_bar_new ();
+        override_style (panel->priv->status_menubar);
+        gtk_widget_show (panel->priv->status_menubar);
+        gtk_box_pack_end (GTK_BOX (panel->priv->right_hbox), GTK_WIDGET (panel->priv->status_menubar), 
FALSE, FALSE, 0);
+
+        if (!panel->priv->display_is_local || get_show_restart_buttons (panel)) {
+                add_shutdown_menu (panel);
+        }
+
+        add_battery_menu (panel);
+
+        /* FIXME: we should only show hostname on panel when connected
+           to a remote host */
+        if (0) {
+                panel->priv->hostname_label = gtk_label_new (g_get_host_name ());
+                gtk_box_pack_start (GTK_BOX (panel->priv->hbox), panel->priv->hostname_label, FALSE, FALSE, 
6);
+                gtk_widget_show (panel->priv->hostname_label);
+        }
+
+        panel->priv->progress = 0.0;
+        panel->priv->animation_timer = gdm_timer_new ();
+        g_signal_connect_swapped (panel->priv->animation_timer,
+                                  "tick",
+                                  G_CALLBACK (on_animation_tick),
+                                  panel);
+
+        gdm_profile_end (NULL);
+}
+
+static GObject *
+gdm_greeter_panel_constructor (GType                  type,
+                               guint                  n_construct_properties,
+                               GObjectConstructParam *construct_properties)
+{
+        GdmGreeterPanel      *greeter_panel;
+
+        gdm_profile_start (NULL);
+
+        greeter_panel = GDM_GREETER_PANEL (G_OBJECT_CLASS (gdm_greeter_panel_parent_class)->constructor 
(type,
+                                                                                                         
n_construct_properties,
+                                                                                                         
construct_properties));
+
+        setup_panel (greeter_panel);
+
+        gdm_profile_end (NULL);
+
+        return G_OBJECT (greeter_panel);
+}
+
+static void
+gdm_greeter_panel_init (GdmGreeterPanel *panel)
+{
+        panel->priv = GDM_GREETER_PANEL_GET_PRIVATE (panel);
+
+}
+
+static void
+gdm_greeter_panel_finalize (GObject *object)
+{
+        GdmGreeterPanel *greeter_panel;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_GREETER_PANEL (object));
+
+        greeter_panel = GDM_GREETER_PANEL (object);
+
+        g_return_if_fail (greeter_panel->priv != NULL);
+
+        g_signal_handlers_disconnect_by_func (object, on_animation_tick, greeter_panel);
+        g_object_unref (greeter_panel->priv->animation_timer);
+
+        G_OBJECT_CLASS (gdm_greeter_panel_parent_class)->finalize (object);
+}
+
+static void
+gdm_greeter_panel_class_init (GdmGreeterPanelClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+        object_class->get_property = gdm_greeter_panel_get_property;
+        object_class->set_property = gdm_greeter_panel_set_property;
+        object_class->constructor = gdm_greeter_panel_constructor;
+        object_class->dispose = gdm_greeter_panel_dispose;
+        object_class->finalize = gdm_greeter_panel_finalize;
+
+        widget_class->realize = gdm_greeter_panel_real_realize;
+        widget_class->unrealize = gdm_greeter_panel_real_unrealize;
+        widget_class->get_preferred_width = gdm_greeter_panel_real_get_preferred_width;
+        widget_class->get_preferred_height = gdm_greeter_panel_real_get_preferred_height;
+        widget_class->show = gdm_greeter_panel_real_show;
+        widget_class->hide = gdm_greeter_panel_real_hide;
+
+        g_object_class_install_property (object_class,
+                                         PROP_MONITOR,
+                                         g_param_spec_int ("monitor",
+                                                           "Xinerama monitor",
+                                                           "The monitor (in terms of Xinerama) which the 
window is on",
+                                                           0,
+                                                           G_MAXINT,
+                                                           0,
+                                                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+        g_object_class_install_property (object_class,
+                                         PROP_DISPLAY_IS_LOCAL,
+                                         g_param_spec_boolean ("display-is-local",
+                                                               "display is local",
+                                                               "display is local",
+                                                               FALSE,
+                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+        signals [DISCONNECTED] =
+                g_signal_new ("disconnected",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmGreeterPanelClass, disconnected),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE,
+                              0);
+
+        g_type_class_add_private (klass, sizeof (GdmGreeterPanelPrivate));
+}
+
+GtkWidget *
+gdm_greeter_panel_new (GdkScreen *screen,
+                       int        monitor,
+                       gboolean   is_local)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_GREETER_PANEL,
+                               "screen", screen,
+                               "monitor", monitor,
+                               "display-is-local", is_local,
+                               NULL);
+
+        return GTK_WIDGET (object);
+}
diff --git a/gui/simple-greeter/gdm-greeter-panel.h b/gui/simple-greeter/gdm-greeter-panel.h
new file mode 100644
index 0000000..8724c2d
--- /dev/null
+++ b/gui/simple-greeter/gdm-greeter-panel.h
@@ -0,0 +1,72 @@
+/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef __GDM_GREETER_PANEL_H
+#define __GDM_GREETER_PANEL_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_GREETER_PANEL         (gdm_greeter_panel_get_type ())
+#define GDM_GREETER_PANEL(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_GREETER_PANEL, 
GdmGreeterPanel))
+#define GDM_GREETER_PANEL_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_GREETER_PANEL, 
GdmGreeterPanelClass))
+#define GDM_IS_GREETER_PANEL(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_GREETER_PANEL))
+#define GDM_IS_GREETER_PANEL_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_GREETER_PANEL))
+#define GDM_GREETER_PANEL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_GREETER_PANEL, 
GdmGreeterPanelClass))
+
+typedef struct GdmGreeterPanelPrivate GdmGreeterPanelPrivate;
+
+typedef struct
+{
+        GtkWindow               parent;
+        GdmGreeterPanelPrivate *priv;
+} GdmGreeterPanel;
+
+typedef struct
+{
+        GtkWindowClass   parent_class;
+
+        void (* language_selected)           (GdmGreeterPanel *panel,
+                                              const char      *text);
+
+        void (* session_selected)            (GdmGreeterPanel *panel,
+                                              const char      *text);
+        void (* disconnected)                (GdmGreeterPanel *panel);
+} GdmGreeterPanelClass;
+
+GType                  gdm_greeter_panel_get_type                       (void);
+
+GtkWidget            * gdm_greeter_panel_new                            (GdkScreen *screen,
+                                                                         int        monitor,
+                                                                         gboolean   is_local);
+
+void                   gdm_greeter_panel_show_user_options              (GdmGreeterPanel *panel);
+void                   gdm_greeter_panel_hide_user_options              (GdmGreeterPanel *panel);
+void                   gdm_greeter_panel_reset                          (GdmGreeterPanel *panel);
+
+void                   gdm_greeter_panel_set_default_language_name      (GdmGreeterPanel *panel,
+                                                                         const char      *language_name);
+void                   gdm_greeter_panel_set_default_session_name       (GdmGreeterPanel *panel,
+                                                                         const char      *session_name);
+G_END_DECLS
+
+#endif /* __GDM_GREETER_PANEL_H */
diff --git a/gui/simple-greeter/gdm-greeter-session.c b/gui/simple-greeter/gdm-greeter-session.c
new file mode 100644
index 0000000..5e23fdb
--- /dev/null
+++ b/gui/simple-greeter/gdm-greeter-session.c
@@ -0,0 +1,698 @@
+/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 "gdm-client.h"
+
+#include "gdm-greeter-session.h"
+#include "gdm-greeter-panel.h"
+#include "gdm-greeter-login-window.h"
+#include "gdm-user-chooser-widget.h"
+
+#include "gdm-profile.h"
+
+#define GDM_GREETER_SESSION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_GREETER_SESSION, 
GdmGreeterSessionPrivate))
+
+#define MAX_LOGIN_TRIES 3
+
+struct GdmGreeterSessionPrivate
+{
+        GdmClient             *client;
+        GdmUserVerifier       *user_verifier;
+        GdmRemoteGreeter      *remote_greeter;
+        GdmGreeter            *greeter;
+
+
+        GtkWidget             *login_window;
+        GtkWidget             *panel;
+
+        guint                  num_tries;
+};
+
+enum {
+        PROP_0,
+};
+
+static void     gdm_greeter_session_class_init  (GdmGreeterSessionClass *klass);
+static void     gdm_greeter_session_init        (GdmGreeterSession      *greeter_session);
+static void     gdm_greeter_session_finalize    (GObject                *object);
+
+G_DEFINE_TYPE (GdmGreeterSession, gdm_greeter_session, G_TYPE_OBJECT)
+
+static gpointer session_object = NULL;
+
+static void
+on_info (GdmClient         *client,
+         const char        *service_name,
+         const char        *text,
+         GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: Info: %s", text);
+
+        gdm_greeter_login_window_info (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), service_name, 
text);
+}
+
+static void
+on_problem (GdmClient         *client,
+            const char        *service_name,
+            const char        *text,
+            GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: Problem: %s", text);
+
+        gdm_greeter_login_window_problem (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), 
service_name, text);
+}
+
+static void
+on_service_unavailable (GdmClient         *client,
+                        const char        *service_name,
+                        GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: Service Unavailable: %s", service_name);
+
+        gdm_greeter_login_window_service_unavailable (GDM_GREETER_LOGIN_WINDOW 
(session->priv->login_window), service_name);
+}
+
+static void
+on_conversation_started (GdmClient         *client,
+                         const char        *service_name,
+                         GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: Ready");
+
+        gdm_greeter_login_window_ready (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window),
+                                        service_name);
+}
+
+static void
+on_conversation_stopped (GdmClient         *client,
+                         const char        *service_name,
+                         GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: Conversation '%s' stopped", service_name);
+
+        gdm_greeter_login_window_conversation_stopped (GDM_GREETER_LOGIN_WINDOW 
(session->priv->login_window),
+                                                       service_name);
+}
+
+static void
+on_reset (GdmClient         *client,
+          GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: Reset");
+
+        session->priv->num_tries = 0;
+
+        gdm_greeter_login_window_reset (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window));
+}
+
+static void
+show_or_hide_user_options (GdmGreeterSession *session,
+                           const char        *username)
+{
+    if (username != NULL && strcmp (username, GDM_USER_CHOOSER_USER_OTHER) != 0) {
+            //gdm_greeter_panel_show_user_options (GDM_GREETER_PANEL (session->priv->panel));
+    } else {
+            //gdm_greeter_panel_hide_user_options (GDM_GREETER_PANEL (session->priv->panel));
+    }
+}
+
+static void
+on_selected_user_changed (GdmClient         *client,
+                          const char        *text,
+                          GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: selected user changed: %s", text);
+        show_or_hide_user_options (session, text);
+}
+
+static void
+on_default_language_name_changed (GdmClient         *client,
+                                  const char        *text,
+                                  GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: default language name changed: %s", text);
+}
+
+static void
+on_default_session_name_changed (GdmClient         *client,
+                                 const char        *text,
+                                 GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: default session name changed: %s", text);
+        gdm_greeter_login_window_set_default_session_name (GDM_GREETER_LOGIN_WINDOW 
(session->priv->login_window), text);
+}
+
+static void
+on_timed_login_requested (GdmClient         *client,
+                          const char        *text,
+                          int                delay,
+                          GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: timed login requested for user %s (in %d seconds)", text, delay);
+        gdm_greeter_login_window_request_timed_login (GDM_GREETER_LOGIN_WINDOW 
(session->priv->login_window), text, delay);
+}
+
+static void
+on_session_opened (GdmClient         *client,
+                   const char        *service_name,
+                   GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: session opened");
+        gdm_greeter_login_window_session_opened (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), 
service_name);
+}
+
+static void
+on_info_query (GdmClient         *client,
+               const char        *service_name,
+               const char        *text,
+               GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: Info query: %s", text);
+
+        gdm_greeter_login_window_info_query (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), 
service_name, text);
+}
+
+static void
+on_secret_info_query (GdmClient         *client,
+                      const char        *service_name,
+                      const char        *text,
+                      GdmGreeterSession *session)
+{
+        g_debug ("GdmGreeterSession: Secret info query: %s", text);
+
+        gdm_greeter_login_window_secret_info_query (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), 
service_name, text);
+}
+
+static void
+on_begin_auto_login (GdmGreeterLoginWindow *login_window,
+                     const char            *username,
+                     GdmGreeterSession     *session)
+{
+        gdm_greeter_call_begin_auto_login_sync (session->priv->greeter,
+                                                username,
+                                                NULL,
+                                                NULL);
+}
+
+static void
+get_user_verifier (GdmGreeterSession     *session,
+                   const char            *username)
+{
+        GError *error = NULL;
+
+        g_clear_object (&session->priv->user_verifier);
+
+        if (username != NULL) {
+                session->priv->user_verifier = gdm_client_open_reauthentication_channel_sync 
(session->priv->client,
+                                                                                              username,
+                                                                                              NULL,
+                                                                                              &error);
+
+                if (error != NULL) {
+                        g_debug ("GdmGreeterSession: could not get reauthentication channel for user %s: 
%s", username, error->message);
+                        g_clear_error (&error);
+                }
+        }
+
+        if (session->priv->user_verifier == NULL) {
+                session->priv->user_verifier = gdm_client_get_user_verifier_sync (session->priv->client,
+                                                                                  NULL,
+                                                                                  &error);
+
+                if (error != NULL) {
+                        g_debug ("GdmGreeterSession: could not get user verifier %s", error->message);
+                        g_clear_error (&error);
+                }
+
+                if (session->priv->user_verifier == NULL) {
+
+                        return;
+                }
+        }
+        g_signal_connect (session->priv->user_verifier,
+                          "info-query",
+                          G_CALLBACK (on_info_query),
+                          session);
+        g_signal_connect (session->priv->user_verifier,
+                          "secret-info-query",
+                          G_CALLBACK (on_secret_info_query),
+                          session);
+        g_signal_connect (session->priv->user_verifier,
+                          "info",
+                          G_CALLBACK (on_info),
+                          session);
+        g_signal_connect (session->priv->user_verifier,
+                          "problem",
+                          G_CALLBACK (on_problem),
+                          session);
+        g_signal_connect (session->priv->user_verifier,
+                          "service-unavailable",
+                          G_CALLBACK (on_service_unavailable),
+                          session);
+        g_signal_connect (session->priv->user_verifier,
+                          "conversation-started",
+                          G_CALLBACK (on_conversation_started),
+                          session);
+        g_signal_connect (session->priv->user_verifier,
+                          "conversation-stopped",
+                          G_CALLBACK (on_conversation_stopped),
+                          session);
+        g_signal_connect (session->priv->user_verifier,
+                          "reset",
+                          G_CALLBACK (on_reset),
+                          session);
+        g_signal_connect (session->priv->greeter,
+                          "selected-user-changed",
+                          G_CALLBACK (on_selected_user_changed),
+                          session);
+        g_signal_connect (session->priv->greeter,
+                          "default-language-name-changed",
+                          G_CALLBACK (on_default_language_name_changed),
+                          session);
+        g_signal_connect (session->priv->greeter,
+                          "default-session-name-changed",
+                          G_CALLBACK (on_default_session_name_changed),
+                          session);
+        g_signal_connect (session->priv->greeter,
+                          "timed-login-requested",
+                          G_CALLBACK (on_timed_login_requested),
+                          session);
+        g_signal_connect (session->priv->greeter,
+                          "session-opened",
+                          G_CALLBACK (on_session_opened),
+                          session);
+}
+
+static void
+on_begin_verification (GdmGreeterLoginWindow *login_window,
+                       const char            *service_name,
+                       GdmGreeterSession     *session)
+{
+        get_user_verifier (session, NULL);
+        gdm_user_verifier_call_begin_verification_sync (session->priv->user_verifier,
+                                                        service_name,
+                                                        NULL,
+                                                        NULL);
+}
+
+static void
+on_begin_verification_for_user (GdmGreeterLoginWindow *login_window,
+                                const char            *service_name,
+                                const char            *username,
+                                GdmGreeterSession     *session)
+{
+        get_user_verifier (session, NULL);
+        gdm_user_verifier_call_begin_verification_for_user_sync (session->priv->user_verifier,
+                                                                 service_name,
+                                                                 username,
+                                                                 NULL,
+                                                                 NULL);
+}
+
+static void
+on_query_answer (GdmGreeterLoginWindow *login_window,
+                 const char            *service_name,
+                 const char            *text,
+                 GdmGreeterSession     *session)
+{
+        gdm_user_verifier_call_answer_query_sync (session->priv->user_verifier,
+                                                  service_name,
+                                                  text,
+                                                  NULL,
+                                                  NULL);
+}
+
+static void
+on_select_session (GdmGreeterLoginWindow *login_window,
+                   const char            *text,
+                   GdmGreeterSession     *session)
+{
+        gdm_greeter_call_select_session_sync (session->priv->greeter,
+                                              text,
+                                              NULL,
+                                              NULL);
+}
+
+static void
+on_select_user (GdmGreeterLoginWindow *login_window,
+                const char            *text,
+                GdmGreeterSession     *session)
+{
+        show_or_hide_user_options (session, text);
+        gdm_greeter_call_select_user_sync (session->priv->greeter,
+                                           text,
+                                           NULL,
+                                           NULL);
+}
+
+static void
+on_cancelled (GdmGreeterLoginWindow *login_window,
+              GdmGreeterSession     *session)
+{
+        gdm_user_verifier_call_cancel_sync (session->priv->user_verifier, NULL, NULL);
+}
+
+static void
+on_disconnected (GdmGreeterSession     *session)
+{
+        if (session->priv->remote_greeter != NULL) {
+                gdm_remote_greeter_call_disconnect_sync (session->priv->remote_greeter, NULL, NULL);
+        }
+}
+
+static void
+on_start_session (GdmGreeterLoginWindow *login_window,
+                  const char            *service_name,
+                  GdmGreeterSession     *session)
+{
+        gdm_greeter_call_start_session_when_ready_sync (session->priv->greeter, service_name, TRUE, NULL, 
NULL);
+}
+
+static int
+get_tallest_monitor_at_point (GdkScreen *screen,
+                              int        x,
+                              int        y)
+{
+        cairo_rectangle_int_t area;
+        cairo_region_t *region;
+        int i;
+        int monitor;
+        int n_monitors;
+        int tallest_height;
+
+        tallest_height = 0;
+        n_monitors = gdk_screen_get_n_monitors (screen);
+        monitor = -1;
+        for (i = 0; i < n_monitors; i++) {
+                gdk_screen_get_monitor_geometry (screen, i, &area);
+                region = cairo_region_create_rectangle (&area);
+
+                if (cairo_region_contains_point (region, x, y)) {
+                        if (area.height > tallest_height) {
+                                monitor = i;
+                                tallest_height = area.height;
+                        }
+                }
+                cairo_region_destroy (region);
+        }
+
+        if (monitor == -1) {
+                monitor = gdk_screen_get_monitor_at_point (screen, x, y);
+        }
+
+        return monitor;
+}
+
+static void
+toggle_panel (GdmGreeterSession *session,
+              gboolean           enabled)
+{
+        gdm_profile_start (NULL);
+
+        if (enabled) {
+                GdkDisplay *display;
+                GdkScreen  *screen;
+                int         monitor;
+                int         x, y;
+                gboolean    is_local;
+
+                display = gdk_display_get_default ();
+                gdk_display_get_pointer (display, &screen, &x, &y, NULL);
+
+                monitor = get_tallest_monitor_at_point (screen, x, y);
+
+                is_local = session->priv->remote_greeter != NULL;
+                session->priv->panel = gdm_greeter_panel_new (screen, monitor, is_local);
+
+                g_signal_connect_swapped (session->priv->panel,
+                                          "disconnected",
+                                          G_CALLBACK (on_disconnected),
+                                          session);
+
+                gtk_widget_show (session->priv->panel);
+        } else {
+                gtk_widget_destroy (session->priv->panel);
+                session->priv->panel = NULL;
+        }
+
+        gdm_profile_end (NULL);
+}
+
+static void
+toggle_login_window (GdmGreeterSession *session,
+                     gboolean           enabled)
+{
+        gdm_profile_start (NULL);
+
+        if (enabled) {
+                gboolean is_local;
+
+                is_local = session->priv->remote_greeter != NULL;
+                g_debug ("GdmGreeterSession: Starting a login window local:%d", is_local);
+                session->priv->login_window = gdm_greeter_login_window_new (is_local);
+                g_signal_connect (session->priv->login_window,
+                                  "begin-auto-login",
+                                  G_CALLBACK (on_begin_auto_login),
+                                  session);
+                g_signal_connect (session->priv->login_window,
+                                  "begin-verification",
+                                  G_CALLBACK (on_begin_verification),
+                                  session);
+                g_signal_connect (session->priv->login_window,
+                                  "begin-verification-for-user",
+                                  G_CALLBACK (on_begin_verification_for_user),
+                                  session);
+                g_signal_connect (session->priv->login_window,
+                                  "query-answer",
+                                  G_CALLBACK (on_query_answer),
+                                  session);
+                g_signal_connect (session->priv->login_window,
+                                  "user-selected",
+                                  G_CALLBACK (on_select_user),
+                                  session);
+                g_signal_connect (session->priv->login_window,
+                                  "session-selected",
+                                  G_CALLBACK (on_select_session),
+                                  session);
+                g_signal_connect (session->priv->login_window,
+                                  "cancelled",
+                                  G_CALLBACK (on_cancelled),
+                                  session);
+                g_signal_connect (session->priv->login_window,
+                                  "start-session",
+                                  G_CALLBACK (on_start_session),
+                                  session);
+                gtk_widget_show (session->priv->login_window);
+        } else {
+                gtk_widget_destroy (session->priv->login_window);
+                session->priv->login_window = NULL;
+        }
+        gdm_profile_end (NULL);
+}
+
+gboolean
+gdm_greeter_session_start (GdmGreeterSession *session,
+                           GError           **error)
+{
+        g_return_val_if_fail (GDM_IS_GREETER_SESSION (session), FALSE);
+
+        gdm_profile_start (NULL);
+
+
+        session->priv->greeter = gdm_client_get_greeter_sync (session->priv->client,
+                                                              NULL,
+                                                              error);
+
+        if (session->priv->greeter == NULL) {
+                return FALSE;
+        }
+
+        session->priv->remote_greeter = gdm_client_get_remote_greeter_sync (session->priv->client,
+                                                                            NULL,
+                                                                            error);
+
+
+        toggle_panel (session, TRUE);
+        toggle_login_window (session, TRUE);
+
+        gdm_profile_end (NULL);
+
+        return TRUE;
+}
+
+void
+gdm_greeter_session_stop (GdmGreeterSession *session)
+{
+        g_return_if_fail (GDM_IS_GREETER_SESSION (session));
+
+        toggle_panel (session, FALSE);
+        toggle_login_window (session, FALSE);
+}
+
+static void
+gdm_greeter_session_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_greeter_session_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_greeter_session_constructor (GType                  type,
+                                 guint                  n_construct_properties,
+                                 GObjectConstructParam *construct_properties)
+{
+        GdmGreeterSession      *greeter_session;
+
+        greeter_session = GDM_GREETER_SESSION (G_OBJECT_CLASS 
(gdm_greeter_session_parent_class)->constructor (type,
+                                                                                                             
  n_construct_properties,
+                                                                                                             
  construct_properties));
+
+        return G_OBJECT (greeter_session);
+}
+
+static void
+gdm_greeter_session_dispose (GObject *object)
+{
+        g_debug ("GdmGreeterSession: Disposing greeter_session");
+
+        G_OBJECT_CLASS (gdm_greeter_session_parent_class)->dispose (object);
+}
+
+static void
+gdm_greeter_session_class_init (GdmGreeterSessionClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->get_property = gdm_greeter_session_get_property;
+        object_class->set_property = gdm_greeter_session_set_property;
+        object_class->constructor = gdm_greeter_session_constructor;
+        object_class->dispose = gdm_greeter_session_dispose;
+        object_class->finalize = gdm_greeter_session_finalize;
+
+        g_type_class_add_private (klass, sizeof (GdmGreeterSessionPrivate));
+}
+
+static void
+gdm_greeter_session_event_handler (GdkEvent          *event,
+                                   GdmGreeterSession *session)
+{
+        g_assert (GDM_IS_GREETER_SESSION (session));
+
+        if (event->type == GDK_KEY_PRESS) {
+                GdkEventKey *key_event;
+
+                key_event = (GdkEventKey *) event;
+                if (session->priv->panel != NULL) {
+                        if (gtk_window_activate_key (GTK_WINDOW (session->priv->panel),
+                                                     key_event)) {
+                                gtk_window_present_with_time (GTK_WINDOW (session->priv->panel),
+                                                              key_event->time);
+                                return;
+                        }
+                }
+
+                if (session->priv->login_window != NULL) {
+                        if (gtk_window_activate_key (GTK_WINDOW (session->priv->login_window),
+                                                     ((GdkEventKey *) event))) {
+                                gtk_window_present_with_time (GTK_WINDOW (session->priv->login_window),
+                                                              key_event->time);
+                                return;
+                        }
+                }
+        }
+
+        gtk_main_do_event (event);
+}
+
+static void
+gdm_greeter_session_init (GdmGreeterSession *session)
+{
+        gdm_profile_start (NULL);
+
+        session->priv = GDM_GREETER_SESSION_GET_PRIVATE (session);
+
+        session->priv->client = gdm_client_new ();
+        /* We want to listen for panel mnemonics even if the
+         * login window is focused, so we intercept them here.
+         */
+        gdk_event_handler_set ((GdkEventFunc) gdm_greeter_session_event_handler,
+                               session, NULL);
+
+        gdm_profile_end (NULL);
+}
+
+static void
+gdm_greeter_session_finalize (GObject *object)
+{
+        GdmGreeterSession *greeter_session;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_GREETER_SESSION (object));
+
+        greeter_session = GDM_GREETER_SESSION (object);
+
+        g_return_if_fail (greeter_session->priv != NULL);
+
+        G_OBJECT_CLASS (gdm_greeter_session_parent_class)->finalize (object);
+}
+
+GdmGreeterSession *
+gdm_greeter_session_new (void)
+{
+        if (session_object != NULL) {
+                g_object_ref (session_object);
+        } else {
+                session_object = g_object_new (GDM_TYPE_GREETER_SESSION, NULL);
+                g_object_add_weak_pointer (session_object,
+                                           (gpointer *) &session_object);
+        }
+
+        return GDM_GREETER_SESSION (session_object);
+}
diff --git a/gui/simple-greeter/gdm-greeter-session.h b/gui/simple-greeter/gdm-greeter-session.h
new file mode 100644
index 0000000..8036e09
--- /dev/null
+++ b/gui/simple-greeter/gdm-greeter-session.h
@@ -0,0 +1,58 @@
+/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef __GDM_GREETER_SESSION_H
+#define __GDM_GREETER_SESSION_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_GREETER_SESSION         (gdm_greeter_session_get_type ())
+#define GDM_GREETER_SESSION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_GREETER_SESSION, 
GdmGreeterSession))
+#define GDM_GREETER_SESSION_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_GREETER_SESSION, 
GdmGreeterSessionClass))
+#define GDM_IS_GREETER_SESSION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_GREETER_SESSION))
+#define GDM_IS_GREETER_SESSION_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_GREETER_SESSION))
+#define GDM_GREETER_SESSION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_GREETER_SESSION, 
GdmGreeterSessionClass))
+
+typedef struct GdmGreeterSessionPrivate GdmGreeterSessionPrivate;
+
+typedef struct
+{
+        GObject                   parent;
+        GdmGreeterSessionPrivate *priv;
+} GdmGreeterSession;
+
+typedef struct
+{
+        GObjectClass   parent_class;
+} GdmGreeterSessionClass;
+
+GType                  gdm_greeter_session_get_type                       (void);
+
+GdmGreeterSession    * gdm_greeter_session_new                            (void);
+
+gboolean               gdm_greeter_session_start                          (GdmGreeterSession *session,
+                                                                           GError           **error);
+void                   gdm_greeter_session_stop                           (GdmGreeterSession *session);
+
+G_END_DECLS
+
+#endif /* __GDM_GREETER_SESSION_H */
diff --git a/gui/simple-greeter/gdm-option-widget.c b/gui/simple-greeter/gdm-option-widget.c
new file mode 100644
index 0000000..ed0a112
--- /dev/null
+++ b/gui/simple-greeter/gdm-option-widget.c
@@ -0,0 +1,1144 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *  Written by: Ray Strode <rstrode redhat com>
+ *              William Jon McCann <mccann jhu edu>
+ */
+
+#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 "gdm-option-widget.h"
+
+#define GDM_OPTION_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_OPTION_WIDGET, 
GdmOptionWidgetPrivate))
+
+#define GDM_OPTION_WIDGET_RC_STRING \
+"style \"gdm-option-widget-style\"" \
+"{" \
+"  GtkComboBox::appears-as-list = 1" \
+"}" \
+"widget_class \"*<GdmOptionWidget>.*.GtkComboBox\" style \"gdm-option-widget-style\""
+
+struct GdmOptionWidgetPrivate
+{
+        GtkWidget                *label;
+        GtkWidget                *image;
+        char                     *label_text;
+        char                     *icon_name;
+        char                     *default_item_id;
+
+        GtkWidget                *items_combo_box;
+        GtkListStore             *list_store;
+
+        GtkTreeModelFilter       *model_filter;
+        GtkTreeModelSort         *model_sorter;
+
+        GtkTreeRowReference      *active_row;
+        GtkTreeRowReference      *top_separator_row;
+        GtkTreeRowReference      *bottom_separator_row;
+
+        gint                     number_of_top_rows;
+        gint                     number_of_middle_rows;
+        gint                     number_of_bottom_rows;
+
+        guint                    check_idle_id;
+};
+
+enum {
+        PROP_0,
+        PROP_LABEL_TEXT,
+        PROP_ICON_NAME,
+        PROP_DEFAULT_ITEM
+};
+
+enum {
+        ACTIVATED = 0,
+        NUMBER_OF_SIGNALS
+};
+
+static guint    signals[NUMBER_OF_SIGNALS];
+
+static void     gdm_option_widget_class_init  (GdmOptionWidgetClass *klass);
+static void     gdm_option_widget_init        (GdmOptionWidget      *option_widget);
+static void     gdm_option_widget_finalize    (GObject              *object);
+
+G_DEFINE_TYPE (GdmOptionWidget, gdm_option_widget, GTK_TYPE_ALIGNMENT)
+enum {
+        OPTION_NAME_COLUMN = 0,
+        OPTION_COMMENT_COLUMN,
+        OPTION_POSITION_COLUMN,
+        OPTION_ID_COLUMN,
+        NUMBER_OF_OPTION_COLUMNS
+};
+
+static gboolean
+find_item (GdmOptionWidget *widget,
+           const char       *id,
+           GtkTreeIter      *iter)
+{
+        GtkTreeModel *model;
+        gboolean      found_item;
+
+        g_assert (GDM_IS_OPTION_WIDGET (widget));
+        g_assert (id != NULL);
+
+        found_item = FALSE;
+        model = GTK_TREE_MODEL (widget->priv->model_sorter);
+
+        if (!gtk_tree_model_get_iter_first (model, iter)) {
+                return FALSE;
+        }
+
+        do {
+                char *item_id;
+
+                gtk_tree_model_get (model, iter,
+                                    OPTION_ID_COLUMN, &item_id, -1);
+
+                g_assert (item_id != NULL);
+
+                if (strcmp (id, item_id) == 0) {
+                        found_item = TRUE;
+                }
+                g_free (item_id);
+
+        } while (!found_item && gtk_tree_model_iter_next (model, iter));
+
+        return found_item;
+}
+
+static char *
+get_active_item_id (GdmOptionWidget *widget,
+                    GtkTreeIter      *iter)
+{
+        char         *item_id;
+        GtkTreeModel *model;
+        GtkTreePath  *path;
+
+        g_return_val_if_fail (GDM_IS_OPTION_WIDGET (widget), NULL);
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+        item_id = NULL;
+
+        if (widget->priv->active_row == NULL ||
+            !gtk_tree_row_reference_valid (widget->priv->active_row)) {
+                return NULL;
+        }
+
+        path = gtk_tree_row_reference_get_path (widget->priv->active_row);
+        if (gtk_tree_model_get_iter (model, iter, path)) {
+                gtk_tree_model_get (model, iter,
+                                    OPTION_ID_COLUMN, &item_id, -1);
+        };
+        gtk_tree_path_free (path);
+
+        return item_id;
+}
+
+char *
+gdm_option_widget_get_active_item (GdmOptionWidget *widget)
+{
+        GtkTreeIter iter;
+
+        return get_active_item_id (widget, &iter);
+}
+
+static void
+activate_from_item_id (GdmOptionWidget *widget,
+                       const char      *item_id)
+{
+        GtkTreeIter   iter;
+
+        if (item_id == NULL) {
+                if (widget->priv->active_row != NULL) {
+                    gtk_tree_row_reference_free (widget->priv->active_row);
+                    widget->priv->active_row = NULL;
+                }
+
+                gtk_combo_box_set_active (GTK_COMBO_BOX (widget->priv->items_combo_box), -1);
+                return;
+        }
+
+        if (!find_item (widget, item_id, &iter)) {
+                g_critical ("Tried to activate non-existing item from option widget");
+                return;
+        }
+
+        gtk_combo_box_set_active_iter (GTK_COMBO_BOX (widget->priv->items_combo_box),
+                                       &iter);
+}
+
+static void
+activate_from_row (GdmOptionWidget    *widget,
+                   GtkTreeRowReference *row)
+{
+        g_assert (row != NULL);
+        g_assert (gtk_tree_row_reference_valid (row));
+
+        if (widget->priv->active_row != NULL) {
+                gtk_tree_row_reference_free (widget->priv->active_row);
+                widget->priv->active_row = NULL;
+        }
+
+        widget->priv->active_row = gtk_tree_row_reference_copy (row);
+
+        g_signal_emit (widget, signals[ACTIVATED], 0);
+
+}
+
+static void
+activate_selected_item (GdmOptionWidget *widget)
+{
+        GtkTreeModel        *model;
+        GtkTreeIter          sorted_iter;
+        gboolean             is_already_active;
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+        is_already_active = FALSE;
+
+        if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (widget->priv->items_combo_box), &sorted_iter)) {
+                GtkTreeRowReference *row;
+                GtkTreePath *sorted_path;
+                GtkTreePath *base_path;
+
+                sorted_path = gtk_tree_model_get_path (GTK_TREE_MODEL (widget->priv->model_sorter),
+                                                       &sorted_iter);
+                base_path =
+                    gtk_tree_model_sort_convert_path_to_child_path (widget->priv->model_sorter,
+                                                                    sorted_path);
+                gtk_tree_path_free (sorted_path);
+
+                if (widget->priv->active_row != NULL) {
+                        GtkTreePath *active_path;
+
+                        active_path = gtk_tree_row_reference_get_path (widget->priv->active_row);
+
+                        if (active_path != NULL) {
+                                if (gtk_tree_path_compare  (base_path, active_path) == 0) {
+                                        is_already_active = TRUE;
+                                }
+                                gtk_tree_path_free (active_path);
+                        }
+                }
+                g_assert (base_path != NULL);
+                row = gtk_tree_row_reference_new (model, base_path);
+                gtk_tree_path_free (base_path);
+
+                if (!is_already_active) {
+                    activate_from_row (widget, row);
+                }
+
+                gtk_tree_row_reference_free (row);
+        }
+}
+
+void
+gdm_option_widget_set_active_item (GdmOptionWidget *widget,
+                                   const char      *id)
+{
+        g_return_if_fail (GDM_IS_OPTION_WIDGET (widget));
+
+        activate_from_item_id (widget, id);
+}
+
+char *
+gdm_option_widget_get_default_item (GdmOptionWidget *widget)
+{
+        g_return_val_if_fail (GDM_IS_OPTION_WIDGET (widget), NULL);
+
+        return g_strdup (widget->priv->default_item_id);
+}
+
+void
+gdm_option_widget_set_default_item (GdmOptionWidget *widget,
+                                    const char      *item)
+{
+        char *active;
+
+        g_return_if_fail (GDM_IS_OPTION_WIDGET (widget));
+        g_return_if_fail (item == NULL ||
+                          gdm_option_widget_lookup_item (widget, item,
+                                                         NULL, NULL, NULL));
+
+        if (widget->priv->default_item_id == NULL ||
+            item == NULL ||
+            strcmp (widget->priv->default_item_id, item) != 0) {
+                g_free (widget->priv->default_item_id);
+                widget->priv->default_item_id = NULL;
+
+                if (widget->priv->active_row == NULL || item != NULL) {
+                    activate_from_item_id (widget, item);
+                }
+
+                widget->priv->default_item_id = g_strdup (item);
+
+                g_object_notify (G_OBJECT (widget), "default-item");
+
+        } 
+
+        /* If a row has already been selected, then reset the selection to
+         * the active row.  This way when a user fails to authenticate, any
+         * previously selected value will still be selected.
+         */
+        active = gdm_option_widget_get_active_item (widget);
+
+        if (active != NULL && item != NULL &&
+            strcmp (gdm_option_widget_get_active_item (widget),
+                    item) != 0) {
+                GtkTreeRowReference *row;
+                GtkTreePath         *active_path;
+                GtkTreeModel        *model;
+
+                gdm_option_widget_set_active_item (widget, active);
+                active_path = gtk_tree_row_reference_get_path (widget->priv->active_row);
+                model = GTK_TREE_MODEL (widget->priv->list_store);
+                if (active_path != NULL) {
+                        row = gtk_tree_row_reference_new (model, active_path);
+                        activate_from_row (widget, row);
+                        gtk_tree_path_free (active_path);
+                        gtk_tree_row_reference_free (row);
+                }
+        }
+}
+
+static const char *
+gdm_option_widget_get_label_text (GdmOptionWidget *widget)
+{
+        return widget->priv->label_text;
+}
+
+static void
+gdm_option_widget_set_label_text (GdmOptionWidget *widget,
+                                  const char      *text)
+{
+        if (widget->priv->label_text == NULL ||
+            strcmp (widget->priv->label_text, text) != 0) {
+                g_free (widget->priv->label_text);
+                widget->priv->label_text = g_strdup (text);
+                gtk_widget_set_tooltip_markup (widget->priv->image, text);
+                g_object_notify (G_OBJECT (widget), "label-text");
+        }
+}
+
+static const char *
+gdm_option_widget_get_icon_name (GdmOptionWidget *widget)
+{
+        return widget->priv->icon_name;
+}
+
+static void
+gdm_option_widget_set_icon_name (GdmOptionWidget *widget,
+                                 const char      *name)
+{
+        if (name == NULL && widget->priv->icon_name != NULL) {
+                /* remove icon */
+                g_free (widget->priv->icon_name);
+                widget->priv->icon_name = NULL;
+                gtk_widget_hide (widget->priv->image);
+                gtk_image_clear (GTK_IMAGE (widget->priv->image));
+                g_object_notify (G_OBJECT (widget), "icon-name");
+        } else if (name != NULL && widget->priv->icon_name == NULL) {
+                /* add icon */
+                widget->priv->icon_name = g_strdup (name);
+                gtk_widget_show (widget->priv->image);
+                gtk_image_set_from_icon_name (GTK_IMAGE (widget->priv->image), name, GTK_ICON_SIZE_BUTTON);
+                g_object_notify (G_OBJECT (widget), "icon-name");
+        } else if (name != NULL
+                   && widget->priv->icon_name != NULL
+                   && strcmp (widget->priv->icon_name, name) != 0) {
+                /* changed icon */
+                g_free (widget->priv->icon_name);
+                widget->priv->icon_name = g_strdup (name);
+                gtk_image_set_from_icon_name (GTK_IMAGE (widget->priv->image), name, GTK_ICON_SIZE_BUTTON);
+                g_object_notify (G_OBJECT (widget), "icon-name");
+        }
+}
+
+static void
+gdm_option_widget_set_property (GObject        *object,
+                                guint           prop_id,
+                                const GValue   *value,
+                                GParamSpec     *pspec)
+{
+        GdmOptionWidget *self;
+
+        self = GDM_OPTION_WIDGET (object);
+
+        switch (prop_id) {
+        case PROP_LABEL_TEXT:
+                gdm_option_widget_set_label_text (self, g_value_get_string (value));
+                break;
+        case PROP_ICON_NAME:
+                gdm_option_widget_set_icon_name (self, g_value_get_string (value));
+                break;
+        case PROP_DEFAULT_ITEM:
+                gdm_option_widget_set_default_item (self, g_value_get_string (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gdm_option_widget_get_property (GObject        *object,
+                                guint           prop_id,
+                                GValue         *value,
+                                GParamSpec     *pspec)
+{
+        GdmOptionWidget *self;
+
+        self = GDM_OPTION_WIDGET (object);
+
+        switch (prop_id) {
+        case PROP_LABEL_TEXT:
+                g_value_set_string (value,
+                                    gdm_option_widget_get_label_text (self));
+                break;
+        case PROP_ICON_NAME:
+                g_value_set_string (value,
+                                    gdm_option_widget_get_icon_name (self));
+                break;
+        case PROP_DEFAULT_ITEM:
+                g_value_take_string (value,
+                                    gdm_option_widget_get_default_item (self));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static GObject *
+gdm_option_widget_constructor (GType                  type,
+                               guint                  n_construct_properties,
+                               GObjectConstructParam *construct_properties)
+{
+        GdmOptionWidget      *option_widget;
+
+        option_widget = GDM_OPTION_WIDGET (G_OBJECT_CLASS (gdm_option_widget_parent_class)->constructor 
(type,
+                                                                                                         
n_construct_properties,
+                                                                                                         
construct_properties));
+
+        return G_OBJECT (option_widget);
+}
+
+static void
+gdm_option_widget_dispose (GObject *object)
+{
+        GdmOptionWidget *widget;
+
+        widget = GDM_OPTION_WIDGET (object);
+
+        if (widget->priv->top_separator_row != NULL) {
+                gtk_tree_row_reference_free (widget->priv->top_separator_row);
+                widget->priv->top_separator_row = NULL;
+        }
+
+        if (widget->priv->bottom_separator_row != NULL) {
+                gtk_tree_row_reference_free (widget->priv->bottom_separator_row);
+                widget->priv->bottom_separator_row = NULL;
+        }
+
+        if (widget->priv->active_row != NULL) {
+                gtk_tree_row_reference_free (widget->priv->active_row);
+                widget->priv->active_row = NULL;
+        }
+
+        G_OBJECT_CLASS (gdm_option_widget_parent_class)->dispose (object);
+}
+
+static void
+gdm_option_widget_class_init (GdmOptionWidgetClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->get_property = gdm_option_widget_get_property;
+        object_class->set_property = gdm_option_widget_set_property;
+        object_class->constructor = gdm_option_widget_constructor;
+        object_class->dispose = gdm_option_widget_dispose;
+        object_class->finalize = gdm_option_widget_finalize;
+
+        gtk_rc_parse_string (GDM_OPTION_WIDGET_RC_STRING);
+
+        signals [ACTIVATED] = g_signal_new ("activated",
+                                            G_TYPE_FROM_CLASS (object_class),
+                                            G_SIGNAL_RUN_FIRST,
+                                            G_STRUCT_OFFSET (GdmOptionWidgetClass, activated),
+                                            NULL,
+                                            NULL,
+                                            g_cclosure_marshal_VOID__VOID,
+                                            G_TYPE_NONE,
+                                            0);
+
+        g_object_class_install_property (object_class,
+                                         PROP_LABEL_TEXT,
+                                         g_param_spec_string ("label-text",
+                                                              _("Label Text"),
+                                                              _("The text to use as a label"),
+                                                              NULL,
+                                                              (G_PARAM_READWRITE |
+                                                               G_PARAM_CONSTRUCT)));
+        g_object_class_install_property (object_class,
+                                         PROP_ICON_NAME,
+                                         g_param_spec_string ("icon-name",
+                                                              _("Icon name"),
+                                                              _("The icon to use with the label"),
+                                                              NULL,
+                                                              (G_PARAM_READWRITE |
+                                                               G_PARAM_CONSTRUCT)));
+
+        g_object_class_install_property (object_class,
+                                         PROP_DEFAULT_ITEM,
+                                         g_param_spec_string ("default-item",
+                                                              _("Default Item"),
+                                                              _("The ID of the default item"),
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
+
+        g_type_class_add_private (klass, sizeof (GdmOptionWidgetPrivate));
+}
+
+static void
+on_changed (GtkComboBox     *combo_box,
+            GdmOptionWidget *widget)
+{
+        if (widget->priv->default_item_id == NULL) {
+                return;
+        }
+
+        activate_selected_item (widget);
+}
+
+static void
+on_default_item_changed (GdmOptionWidget *widget)
+{
+        gtk_widget_set_sensitive (widget->priv->items_combo_box,
+                                  widget->priv->default_item_id != NULL);
+        gtk_tree_model_filter_refilter (widget->priv->model_filter);
+}
+
+static gboolean
+path_is_row (GdmOptionWidget     *widget,
+             GtkTreeModel        *model,
+             GtkTreePath         *path,
+             GtkTreeRowReference *row)
+{
+        GtkTreePath      *row_path;
+        GtkTreePath      *translated_path;
+        gboolean          is_row;
+
+        row_path = gtk_tree_row_reference_get_path (row);
+
+        if (row_path == NULL) {
+                return FALSE;
+        }
+
+        if (model == GTK_TREE_MODEL (widget->priv->model_sorter)) {
+                GtkTreePath *filtered_path;
+
+                filtered_path = gtk_tree_model_sort_convert_path_to_child_path (widget->priv->model_sorter, 
path);
+
+                translated_path = gtk_tree_model_filter_convert_path_to_child_path 
(widget->priv->model_filter, filtered_path);
+                gtk_tree_path_free (filtered_path);
+        } else if (model == GTK_TREE_MODEL (widget->priv->model_filter)) {
+                translated_path = gtk_tree_model_filter_convert_path_to_child_path 
(widget->priv->model_filter, path);
+        } else {
+                g_assert (model == GTK_TREE_MODEL (widget->priv->list_store));
+                translated_path = gtk_tree_path_copy (path);
+        }
+
+        if (gtk_tree_path_compare (row_path, translated_path) == 0) {
+                is_row = TRUE;
+        } else {
+                is_row = FALSE;
+        }
+        gtk_tree_path_free (translated_path);
+
+        return is_row;
+}
+
+static gboolean
+path_is_top_separator (GdmOptionWidget *widget,
+                       GtkTreeModel    *model,
+                       GtkTreePath     *path)
+{
+        if (widget->priv->top_separator_row != NULL) {
+                if (path_is_row (widget, model, path,
+                                 widget->priv->top_separator_row)) {
+                    return TRUE;
+                }
+        }
+
+        return FALSE;
+}
+
+static gboolean
+path_is_bottom_separator (GdmOptionWidget *widget,
+                          GtkTreeModel    *model,
+                          GtkTreePath     *path)
+{
+        if (widget->priv->bottom_separator_row != NULL) {
+
+                if (path_is_row (widget, model, path,
+                                 widget->priv->bottom_separator_row)) {
+                    return TRUE;
+                }
+        }
+
+        return FALSE;
+}
+
+static gboolean
+path_is_separator (GdmOptionWidget *widget,
+                   GtkTreeModel    *model,
+                   GtkTreePath     *path)
+{
+        return path_is_top_separator (widget, model, path) ||
+               path_is_bottom_separator (widget, model, path);
+}
+
+static gboolean
+gdm_option_widget_check_visibility (GdmOptionWidget *widget)
+{
+        if ((widget->priv->number_of_middle_rows != 0) &&
+            (widget->priv->number_of_top_rows > 0 ||
+             widget->priv->number_of_middle_rows > 1 ||
+             widget->priv->number_of_bottom_rows > 0)) {
+                gtk_widget_show (widget->priv->items_combo_box);
+
+                if (widget->priv->icon_name != NULL) {
+                        gtk_widget_show (widget->priv->image);
+                }
+        } else {
+                gtk_widget_hide (widget->priv->items_combo_box);
+                gtk_widget_hide (widget->priv->image);
+        }
+
+        widget->priv->check_idle_id = 0;
+        return FALSE;
+}
+
+static void
+gdm_option_widget_queue_visibility_check (GdmOptionWidget *widget)
+{
+        if (widget->priv->check_idle_id == 0) {
+                widget->priv->check_idle_id = g_idle_add ((GSourceFunc) gdm_option_widget_check_visibility, 
widget);
+        }
+}
+
+static gboolean
+check_item_visibilty (GtkTreeModel *model,
+                      GtkTreeIter  *iter,
+                      gpointer      data)
+{
+        GdmOptionWidget *widget;
+        GtkTreePath     *path;
+        gboolean         is_top_separator;
+        gboolean         is_bottom_separator;
+        gboolean         is_visible;
+
+        g_assert (GDM_IS_OPTION_WIDGET (data));
+
+        widget = GDM_OPTION_WIDGET (data);
+
+        path = gtk_tree_model_get_path (model, iter);
+        is_top_separator = path_is_top_separator (widget, model, path);
+        is_bottom_separator = path_is_bottom_separator (widget, model, path);
+        gtk_tree_path_free (path);
+
+        if (is_top_separator) {
+                is_visible = widget->priv->number_of_top_rows > 0 &&
+                             widget->priv->number_of_middle_rows > 0;
+        } else if (is_bottom_separator) {
+                is_visible = widget->priv->number_of_bottom_rows > 0 &&
+                             widget->priv->number_of_middle_rows > 0;
+        } else {
+                is_visible = TRUE;
+        }
+
+        gdm_option_widget_queue_visibility_check (widget);
+
+        return is_visible;
+}
+
+static int
+compare_item (GtkTreeModel *model,
+              GtkTreeIter  *a,
+              GtkTreeIter  *b,
+              gpointer      data)
+{
+        GdmOptionWidget *widget;
+        GtkTreePath     *path;
+        gboolean         a_is_separator;
+        gboolean         b_is_separator;
+        char            *name_a;
+        char            *name_b;
+        int              position_a;
+        int              position_b;
+        int              result;
+
+        g_assert (GDM_IS_OPTION_WIDGET (data));
+
+        widget = GDM_OPTION_WIDGET (data);
+
+        gtk_tree_model_get (model, a,
+                            OPTION_NAME_COLUMN, &name_a,
+                            OPTION_POSITION_COLUMN, &position_a,
+                            -1);
+
+        gtk_tree_model_get (model, b,
+                            OPTION_NAME_COLUMN, &name_b,
+                            OPTION_POSITION_COLUMN, &position_b,
+                            -1);
+
+        if (position_a != position_b) {
+                result = position_a - position_b;
+                goto out;
+        }
+
+        if (position_a == GDM_OPTION_WIDGET_POSITION_MIDDLE) {
+                a_is_separator = FALSE;
+        } else {
+                path = gtk_tree_model_get_path (model, a);
+                a_is_separator = path_is_separator (widget, model, path);
+                gtk_tree_path_free (path);
+        }
+
+        if (position_b == GDM_OPTION_WIDGET_POSITION_MIDDLE) {
+                b_is_separator = FALSE;
+        } else {
+                path = gtk_tree_model_get_path (model, b);
+                b_is_separator = path_is_separator (widget, model, path);
+                gtk_tree_path_free (path);
+        }
+
+        if (a_is_separator && b_is_separator) {
+                result = 0;
+                goto out;
+        }
+
+        if (!a_is_separator && !b_is_separator) {
+            result = g_utf8_collate (name_a, name_b);
+            goto out;
+        }
+
+        g_assert (position_a == position_b);
+        g_assert (position_a != GDM_OPTION_WIDGET_POSITION_MIDDLE);
+
+        result = a_is_separator - b_is_separator;
+
+        if (position_a == GDM_OPTION_WIDGET_POSITION_BOTTOM) {
+                result *= -1;
+        }
+out:
+        g_free (name_a);
+        g_free (name_b);
+
+        return result;
+}
+
+static void
+name_cell_data_func (GtkTreeViewColumn  *tree_column,
+                     GtkCellRenderer    *cell,
+                     GtkTreeModel       *model,
+                     GtkTreeIter        *iter,
+                     GdmOptionWidget   *widget)
+{
+        char    *name;
+        char    *id;
+        char    *markup;
+        gboolean is_default;
+
+        name = NULL;
+        gtk_tree_model_get (model,
+                            iter,
+                            OPTION_ID_COLUMN, &id,
+                            OPTION_NAME_COLUMN, &name,
+                            -1);
+
+        if (widget->priv->default_item_id != NULL &&
+            id != NULL &&
+            strcmp (widget->priv->default_item_id, id) == 0) {
+                is_default = TRUE;
+        } else {
+                is_default = FALSE;
+        }
+        g_free (id);
+        id = NULL;
+
+        markup = g_strdup_printf ("<span size='small'>%s%s%s</span>",
+                                  is_default? "<i>" : "",
+                                  name ? name : "",
+                                  is_default? "</i>" : "");
+        g_free (name);
+
+        g_object_set (cell, "markup", markup, NULL);
+        g_free (markup);
+}
+
+static gboolean
+separator_func (GtkTreeModel *model,
+                GtkTreeIter  *iter,
+                gpointer      data)
+{
+        GdmOptionWidget *widget;
+        GtkTreePath     *path;
+        gboolean         is_separator;
+
+        g_assert (GDM_IS_OPTION_WIDGET (data));
+
+        widget = GDM_OPTION_WIDGET (data);
+
+        path = gtk_tree_model_get_path (model, iter);
+
+        is_separator = path_is_separator (widget, model, path);
+
+        gtk_tree_path_free (path);
+
+        return is_separator;
+}
+
+static void
+add_separators (GdmOptionWidget *widget)
+{
+        GtkTreeIter   iter;
+        GtkTreeModel *model;
+        GtkTreePath  *path;
+
+        g_assert (widget->priv->top_separator_row == NULL);
+        g_assert (widget->priv->bottom_separator_row == NULL);
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        gtk_list_store_insert_with_values (widget->priv->list_store,
+                                           &iter, 0,
+                                           OPTION_ID_COLUMN, "--",
+                                           OPTION_POSITION_COLUMN, GDM_OPTION_WIDGET_POSITION_BOTTOM,
+                                           -1);
+        path = gtk_tree_model_get_path (model, &iter);
+        widget->priv->bottom_separator_row =
+            gtk_tree_row_reference_new (model, path);
+        gtk_tree_path_free (path);
+
+        gtk_list_store_insert_with_values (widget->priv->list_store,
+                                           &iter, 0,
+                                           OPTION_ID_COLUMN, "-",
+                                           OPTION_POSITION_COLUMN, GDM_OPTION_WIDGET_POSITION_TOP,
+                                           -1);
+        path = gtk_tree_model_get_path (model, &iter);
+        widget->priv->top_separator_row =
+            gtk_tree_row_reference_new (model, path);
+        gtk_tree_path_free (path);
+}
+
+static gboolean
+on_combo_box_mnemonic_activate (GtkWidget *widget,
+                                gboolean   arg1,
+                                gpointer   user_data)
+{
+        g_return_val_if_fail (GTK_IS_COMBO_BOX (widget), FALSE);
+        gtk_combo_box_popup (GTK_COMBO_BOX (widget));
+
+        return TRUE;
+}
+
+static void
+gdm_option_widget_init (GdmOptionWidget *widget)
+{
+        GtkWidget         *box;
+        GtkCellRenderer   *renderer;
+
+        widget->priv = GDM_OPTION_WIDGET_GET_PRIVATE (widget);
+
+        gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 0, 0, 0);
+        gtk_alignment_set (GTK_ALIGNMENT (widget), 0.5, 0.5, 0, 0);
+
+        box = gtk_hbox_new (FALSE, 6);
+        gtk_widget_show (box);
+        gtk_container_add (GTK_CONTAINER (widget),
+                           box);
+
+        widget->priv->image = gtk_image_new ();
+        gtk_widget_set_no_show_all (widget->priv->image, TRUE);
+        gtk_box_pack_start (GTK_BOX (box), widget->priv->image, FALSE, FALSE, 0);
+
+        widget->priv->items_combo_box = gtk_combo_box_new ();
+
+        g_signal_connect (widget->priv->items_combo_box,
+                          "changed",
+                          G_CALLBACK (on_changed),
+                          widget);
+
+        /* We disable the combo box until it has a default
+         */
+        gtk_widget_set_sensitive (widget->priv->items_combo_box, FALSE);
+        g_signal_connect (widget,
+                          "notify::default-item",
+                          G_CALLBACK (on_default_item_changed),
+                          NULL);
+
+        gtk_widget_set_no_show_all (widget->priv->items_combo_box, TRUE);
+        gtk_container_add (GTK_CONTAINER (box),
+                           widget->priv->items_combo_box);
+        g_signal_connect (widget->priv->items_combo_box,
+                          "mnemonic-activate",
+                          G_CALLBACK (on_combo_box_mnemonic_activate),
+                          NULL);
+
+        g_assert (NUMBER_OF_OPTION_COLUMNS == 4);
+        widget->priv->list_store = gtk_list_store_new (NUMBER_OF_OPTION_COLUMNS,
+                                                       G_TYPE_STRING,
+                                                       G_TYPE_STRING,
+                                                       G_TYPE_INT,
+                                                       G_TYPE_STRING);
+
+
+        widget->priv->model_filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL 
(widget->priv->list_store), NULL));
+
+        gtk_tree_model_filter_set_visible_func (widget->priv->model_filter,
+                                                check_item_visibilty,
+                                                widget, NULL);
+
+        widget->priv->model_sorter = GTK_TREE_MODEL_SORT (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL 
(widget->priv->model_filter)));
+
+        gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (widget->priv->model_sorter),
+                                         OPTION_ID_COLUMN,
+                                         compare_item,
+                                         widget, NULL);
+
+        gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (widget->priv->model_sorter),
+                                              OPTION_ID_COLUMN,
+                                              GTK_SORT_ASCENDING);
+        gtk_combo_box_set_model (GTK_COMBO_BOX (widget->priv->items_combo_box),
+                                 GTK_TREE_MODEL (widget->priv->model_sorter));
+
+        add_separators (widget);
+        gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (widget->priv->items_combo_box),
+                                              separator_func, widget, NULL);
+
+        /* NAME COLUMN */
+        renderer = gtk_cell_renderer_text_new ();
+        gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget->priv->items_combo_box), renderer, FALSE);
+        gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (widget->priv->items_combo_box),
+                                            renderer,
+                                            (GtkCellLayoutDataFunc) name_cell_data_func,
+                                            widget,
+                                            NULL);
+}
+
+static void
+gdm_option_widget_finalize (GObject *object)
+{
+        GdmOptionWidget *widget;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_OPTION_WIDGET (object));
+
+        widget = GDM_OPTION_WIDGET (object);
+
+        g_return_if_fail (widget->priv != NULL);
+
+        g_free (widget->priv->icon_name);
+        g_free (widget->priv->label_text);
+
+        G_OBJECT_CLASS (gdm_option_widget_parent_class)->finalize (object);
+}
+
+GtkWidget *
+gdm_option_widget_new (const char *label_text)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_OPTION_WIDGET,
+                               "label-text", label_text, NULL);
+
+        return GTK_WIDGET (object);
+}
+
+void
+gdm_option_widget_add_item (GdmOptionWidget         *widget,
+                            const char              *id,
+                            const char              *name,
+                            const char              *comment,
+                            GdmOptionWidgetPosition  position)
+{
+        GtkTreeIter iter;
+
+        g_return_if_fail (GDM_IS_OPTION_WIDGET (widget));
+
+        switch (position) {
+            case GDM_OPTION_WIDGET_POSITION_BOTTOM:
+                widget->priv->number_of_bottom_rows++;
+                break;
+
+            case GDM_OPTION_WIDGET_POSITION_MIDDLE:
+                widget->priv->number_of_middle_rows++;
+                break;
+
+            case GDM_OPTION_WIDGET_POSITION_TOP:
+                widget->priv->number_of_top_rows++;
+                break;
+        }
+
+        gtk_list_store_insert_with_values (widget->priv->list_store,
+                                           &iter, 0,
+                                           OPTION_NAME_COLUMN, name,
+                                           OPTION_COMMENT_COLUMN, comment,
+                                           OPTION_POSITION_COLUMN, (int) position,
+                                           OPTION_ID_COLUMN, id,
+                                           -1);
+        gtk_tree_model_filter_refilter (widget->priv->model_filter);
+}
+
+void
+gdm_option_widget_remove_item (GdmOptionWidget *widget,
+                               const char      *id)
+{
+        GtkTreeModel *model;
+        GtkTreeIter   iter;
+        int           position;
+
+        g_return_if_fail (GDM_IS_OPTION_WIDGET (widget));
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        if (!find_item (widget, id, &iter)) {
+                g_critical ("Tried to remove non-existing item from option widget");
+                return;
+        }
+
+        if (widget->priv->default_item_id != NULL &&
+            strcmp (widget->priv->default_item_id, id) == 0) {
+                g_critical ("Tried to remove default item from option widget");
+                return;
+        }
+
+        gtk_tree_model_get (model, &iter,
+                            OPTION_POSITION_COLUMN, &position,
+                            -1);
+
+        switch ((GdmOptionWidgetPosition) position) {
+            case GDM_OPTION_WIDGET_POSITION_BOTTOM:
+                widget->priv->number_of_bottom_rows--;
+                break;
+
+            case GDM_OPTION_WIDGET_POSITION_MIDDLE:
+                widget->priv->number_of_middle_rows--;
+                break;
+
+            case GDM_OPTION_WIDGET_POSITION_TOP:
+                widget->priv->number_of_top_rows--;
+                break;
+        }
+
+        gtk_list_store_remove (widget->priv->list_store, &iter);
+        gtk_tree_model_filter_refilter (widget->priv->model_filter);
+}
+
+void
+gdm_option_widget_remove_all_items (GdmOptionWidget *widget)
+{
+        GtkTreeIter   iter;
+        GtkTreeModel *model;
+        int           position;
+        gboolean      is_valid;
+
+        g_assert (GDM_IS_OPTION_WIDGET (widget));
+
+        model = GTK_TREE_MODEL (widget->priv->list_store);
+
+        if (!gtk_tree_model_get_iter_first (model, &iter)) {
+                return;
+        }
+
+        do {
+                gtk_tree_model_get (model, &iter,
+                                    OPTION_POSITION_COLUMN, &position,
+                                    -1);
+
+                if ((GdmOptionWidgetPosition) position == GDM_OPTION_WIDGET_POSITION_MIDDLE) {
+                        is_valid = gtk_list_store_remove (widget->priv->list_store,
+                                                          &iter);
+                } else {
+                        is_valid = gtk_tree_model_iter_next (model, &iter);
+                }
+
+
+        } while (is_valid);
+}
+
+gboolean
+gdm_option_widget_lookup_item (GdmOptionWidget          *widget,
+                               const char               *id,
+                               char                    **name,
+                               char                    **comment,
+                               GdmOptionWidgetPosition  *position)
+{
+        GtkTreeIter   iter;
+        char         *active_item_id;
+
+        g_return_val_if_fail (GDM_IS_OPTION_WIDGET (widget), FALSE);
+        g_return_val_if_fail (id != NULL, FALSE);
+
+        active_item_id = get_active_item_id (widget, &iter);
+
+        if (active_item_id == NULL || strcmp (active_item_id, id) != 0) {
+                g_free (active_item_id);
+
+                if (!find_item (widget, id, &iter)) {
+                        return FALSE;
+                }
+        } else {
+                g_free (active_item_id);
+        }
+
+        if (name != NULL) {
+                gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter,
+                                    OPTION_NAME_COLUMN, name, -1);
+        }
+
+        if (comment != NULL) {
+                gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter,
+                                    OPTION_COMMENT_COLUMN, comment, -1);
+        }
+
+        if (position != NULL) {
+                int position_as_int;
+
+                gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter,
+                                    OPTION_POSITION_COLUMN, &position_as_int, -1);
+
+                *position = (GdmOptionWidgetPosition) position_as_int;
+        }
+
+        return TRUE;
+}
diff --git a/gui/simple-greeter/gdm-option-widget.h b/gui/simple-greeter/gdm-option-widget.h
new file mode 100644
index 0000000..63934d8
--- /dev/null
+++ b/gui/simple-greeter/gdm-option-widget.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *  Written by: Ray Strode <rstrode redhat com>
+ *              William Jon McCann <mccann jhu edu>
+ */
+
+#ifndef __GDM_OPTION_WIDGET_H
+#define __GDM_OPTION_WIDGET_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_OPTION_WIDGET         (gdm_option_widget_get_type ())
+#define GDM_OPTION_WIDGET(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_OPTION_WIDGET, 
GdmOptionWidget))
+#define GDM_OPTION_WIDGET_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_OPTION_WIDGET, 
GdmOptionWidgetClass))
+#define GDM_IS_OPTION_WIDGET(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_OPTION_WIDGET))
+#define GDM_IS_OPTION_WIDGET_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_OPTION_WIDGET))
+#define GDM_OPTION_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_OPTION_WIDGET, 
GdmOptionWidgetClass))
+
+typedef struct GdmOptionWidgetPrivate GdmOptionWidgetPrivate;
+
+typedef struct
+{
+        GtkAlignment             parent;
+        GdmOptionWidgetPrivate *priv;
+} GdmOptionWidget;
+
+typedef struct
+{
+        GtkAlignmentClass       parent_class;
+
+        void (* activated)      (GdmOptionWidget *widget);
+} GdmOptionWidgetClass;
+
+typedef enum {
+        GDM_OPTION_WIDGET_POSITION_TOP = 0,
+        GDM_OPTION_WIDGET_POSITION_MIDDLE,
+        GDM_OPTION_WIDGET_POSITION_BOTTOM,
+} GdmOptionWidgetPosition;
+
+GType                  gdm_option_widget_get_type               (void);
+GtkWidget *            gdm_option_widget_new                    (const char *label_text);
+
+void                   gdm_option_widget_add_item               (GdmOptionWidget *widget,
+                                                                 const char       *id,
+                                                                 const char       *name,
+                                                                 const char       *comment,
+                                                                 GdmOptionWidgetPosition position);
+
+void                   gdm_option_widget_remove_item            (GdmOptionWidget *widget,
+                                                                 const char       *id);
+
+void                   gdm_option_widget_remove_all_items       (GdmOptionWidget *widget);
+gboolean               gdm_option_widget_lookup_item            (GdmOptionWidget *widget,
+                                                                 const char       *id,
+                                                                 char            **name,
+                                                                 char            **comment,
+                                                                 GdmOptionWidgetPosition *position);
+
+char *                 gdm_option_widget_get_active_item        (GdmOptionWidget *widget);
+void                   gdm_option_widget_set_active_item        (GdmOptionWidget *widget,
+                                                                 const char       *item);
+char *                 gdm_option_widget_get_default_item       (GdmOptionWidget *widget);
+void                   gdm_option_widget_set_default_item       (GdmOptionWidget *widget,
+                                                                 const char       *item);
+G_END_DECLS
+
+#endif /* __GDM_OPTION_WIDGET_H */
diff --git a/gui/simple-greeter/gdm-remote-login-window.c b/gui/simple-greeter/gdm-remote-login-window.c
new file mode 100644
index 0000000..45a0099
--- /dev/null
+++ b/gui/simple-greeter/gdm-remote-login-window.c
@@ -0,0 +1,314 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann <jmccann 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <glib-object.h>
+
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include "gdm-remote-login-window.h"
+#include "gdm-common.h"
+
+#define GDM_REMOTE_LOGIN_WINDOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), 
GDM_TYPE_REMOTE_LOGIN_WINDOW, GdmRemoteLoginWindowPrivate))
+
+struct GdmRemoteLoginWindowPrivate
+{
+        gboolean connected;
+        char    *hostname;
+        char    *display;
+        GPid     xserver_pid;
+        guint    xserver_watch_id;
+};
+
+enum {
+        PROP_0,
+};
+
+enum {
+        DISCONNECTED,
+        LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void     gdm_remote_login_window_class_init   (GdmRemoteLoginWindowClass *klass);
+static void     gdm_remote_login_window_init         (GdmRemoteLoginWindow      *remote_login_window);
+static void     gdm_remote_login_window_finalize     (GObject                    *object);
+
+G_DEFINE_TYPE (GdmRemoteLoginWindow, gdm_remote_login_window, GTK_TYPE_WINDOW)
+
+static void
+xserver_child_watch (GPid                  pid,
+                     int                   status,
+                     GdmRemoteLoginWindow *login_window)
+{
+        g_debug ("GdmRemoteLoginWindow: **** xserver (pid:%d) done (%s:%d)",
+                 (int) pid,
+                 WIFEXITED (status) ? "status"
+                 : WIFSIGNALED (status) ? "signal"
+                 : "unknown",
+                 WIFEXITED (status) ? WEXITSTATUS (status)
+                 : WIFSIGNALED (status) ? WTERMSIG (status)
+                 : -1);
+
+        g_spawn_close_pid (login_window->priv->xserver_pid);
+
+        login_window->priv->xserver_pid = -1;
+        login_window->priv->xserver_watch_id = 0;
+
+        gtk_widget_destroy (GTK_WIDGET (login_window));
+}
+
+static gboolean
+start_xephyr (GdmRemoteLoginWindow *login_window)
+{
+        GError     *local_error;
+        char      **argv;
+        gboolean    res;
+        gboolean    ret;
+        int         flags;
+        char       *command;
+
+        command = g_strdup_printf ("Xephyr -query %s -parent 0x%x -br -once %s",
+                                   login_window->priv->hostname,
+                                   (unsigned int)GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET 
(login_window))),
+                                   login_window->priv->display);
+        g_debug ("GdmRemoteLoginWindow: Running: %s", command);
+
+        ret = FALSE;
+
+        argv = NULL;
+        local_error = NULL;
+        res = g_shell_parse_argv (command, NULL, &argv, &local_error);
+        if (! res) {
+                g_warning ("GdmRemoteLoginWindow: Unable to parse command: %s", local_error->message);
+                g_error_free (local_error);
+                goto out;
+        }
+
+        flags = G_SPAWN_SEARCH_PATH
+                | G_SPAWN_DO_NOT_REAP_CHILD;
+
+        local_error = NULL;
+        res = g_spawn_async (NULL,
+                             argv,
+                             NULL,
+                             flags,
+                             NULL,
+                             NULL,
+                             &login_window->priv->xserver_pid,
+                             &local_error);
+        g_strfreev (argv);
+
+        if (! res) {
+                g_warning ("GdmRemoteLoginWindow: Unable to run command %s: %s",
+                           command,
+                           local_error->message);
+                g_error_free (local_error);
+                goto out;
+        }
+
+        g_debug ("GdmRemoteLoginWindow: Started: pid=%d command='%s'",
+                 login_window->priv->xserver_pid,
+                 command);
+
+        login_window->priv->xserver_watch_id = g_child_watch_add (login_window->priv->xserver_pid,
+                                                                  (GChildWatchFunc)xserver_child_watch,
+                                                                  login_window);
+        ret = TRUE;
+
+ out:
+        g_free (command);
+
+        return ret;
+}
+
+static gboolean
+start_xdmx (GdmRemoteLoginWindow *login_window)
+{
+        char    *cmd;
+        gboolean res;
+        GError  *error;
+
+        cmd = g_strdup_printf ("Xdmx -query %s -br -once %s",
+                               login_window->priv->hostname,
+                               login_window->priv->display);
+        g_debug ("Running: %s", cmd);
+
+        error = NULL;
+        res = g_spawn_command_line_async (cmd, &error);
+
+        g_free (cmd);
+
+        if (! res) {
+                g_warning ("Could not start Xdmx X server: %s", error->message);
+                g_error_free (error);
+                return FALSE;
+        }
+
+        return TRUE;
+}
+
+gboolean
+gdm_remote_login_window_connect (GdmRemoteLoginWindow *login_window,
+                                 const char           *hostname)
+{
+        gboolean res;
+        char    *title;
+
+        title = g_strdup_printf (_("Remote Login (Connecting to %s…)"), hostname);
+
+        gtk_window_set_title (GTK_WINDOW (login_window), title);
+
+        login_window->priv->hostname = g_strdup (hostname);
+        login_window->priv->display = g_strdup (":300");
+
+        if (0) {
+                res = start_xdmx (login_window);
+        } else {
+                res = start_xephyr (login_window);
+        }
+
+        if (res) {
+                title = g_strdup_printf (_("Remote Login (Connected to %s)"), hostname);
+                gtk_window_set_title (GTK_WINDOW (login_window), title);
+                g_free (title);
+        }
+
+        return res;
+}
+
+static void
+gdm_remote_login_window_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_remote_login_window_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_remote_login_window_constructor (GType                  type,
+                                     guint                  n_construct_properties,
+                                     GObjectConstructParam *construct_properties)
+{
+        GdmRemoteLoginWindow      *login_window;
+
+        login_window = GDM_REMOTE_LOGIN_WINDOW (G_OBJECT_CLASS 
(gdm_remote_login_window_parent_class)->constructor (type,
+                                                                                                             
         n_construct_properties,
+                                                                                                             
         construct_properties));
+
+
+        return G_OBJECT (login_window);
+}
+
+static void
+gdm_remote_login_window_class_init (GdmRemoteLoginWindowClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->get_property = gdm_remote_login_window_get_property;
+        object_class->set_property = gdm_remote_login_window_set_property;
+        object_class->constructor = gdm_remote_login_window_constructor;
+        object_class->finalize = gdm_remote_login_window_finalize;
+
+        signals [DISCONNECTED] =
+                g_signal_new ("disconnected",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmRemoteLoginWindowClass, disconnected),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE,
+                              0);
+
+        g_type_class_add_private (klass, sizeof (GdmRemoteLoginWindowPrivate));
+}
+
+static void
+gdm_remote_login_window_init (GdmRemoteLoginWindow *login_window)
+{
+        login_window->priv = GDM_REMOTE_LOGIN_WINDOW_GET_PRIVATE (login_window);
+
+        gtk_window_set_position (GTK_WINDOW (login_window), GTK_WIN_POS_CENTER_ALWAYS);
+        gtk_window_set_title (GTK_WINDOW (login_window), _("Remote Login"));
+        gtk_window_set_decorated (GTK_WINDOW (login_window), FALSE);
+        gtk_window_set_skip_taskbar_hint (GTK_WINDOW (login_window), TRUE);
+        gtk_window_set_skip_pager_hint (GTK_WINDOW (login_window), TRUE);
+        gtk_window_stick (GTK_WINDOW (login_window));
+        gtk_window_maximize (GTK_WINDOW (login_window));
+        gtk_window_set_icon_name (GTK_WINDOW (login_window), "computer");
+}
+
+static void
+gdm_remote_login_window_finalize (GObject *object)
+{
+        GdmRemoteLoginWindow *login_window;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_REMOTE_LOGIN_WINDOW (object));
+
+        login_window = GDM_REMOTE_LOGIN_WINDOW (object);
+
+        g_return_if_fail (login_window->priv != NULL);
+
+        G_OBJECT_CLASS (gdm_remote_login_window_parent_class)->finalize (object);
+}
+
+GtkWidget *
+gdm_remote_login_window_new (gboolean is_local)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_REMOTE_LOGIN_WINDOW,
+                               NULL);
+
+        return GTK_WIDGET (object);
+}
diff --git a/gui/simple-greeter/gdm-remote-login-window.h b/gui/simple-greeter/gdm-remote-login-window.h
new file mode 100644
index 0000000..8e32658
--- /dev/null
+++ b/gui/simple-greeter/gdm-remote-login-window.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann <jmccann 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef __GDM_REMOTE_LOGIN_WINDOW_H
+#define __GDM_REMOTE_LOGIN_WINDOW_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_REMOTE_LOGIN_WINDOW         (gdm_remote_login_window_get_type ())
+#define GDM_REMOTE_LOGIN_WINDOW(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_REMOTE_LOGIN_WINDOW, 
GdmRemoteLoginWindow))
+#define GDM_REMOTE_LOGIN_WINDOW_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_REMOTE_LOGIN_WINDOW, 
GdmRemoteLoginWindowClass))
+#define GDM_IS_REMOTE_LOGIN_WINDOW(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_REMOTE_LOGIN_WINDOW))
+#define GDM_IS_REMOTE_LOGIN_WINDOW_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_REMOTE_LOGIN_WINDOW))
+#define GDM_REMOTE_LOGIN_WINDOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_REMOTE_LOGIN_WINDOW, 
GdmRemoteLoginWindowClass))
+
+typedef struct GdmRemoteLoginWindowPrivate GdmRemoteLoginWindowPrivate;
+
+typedef struct
+{
+        GtkWindow                    parent;
+        GdmRemoteLoginWindowPrivate *priv;
+} GdmRemoteLoginWindow;
+
+typedef struct
+{
+        GtkWindowClass   parent_class;
+
+        /* signals */
+        void (* disconnected)                (GdmRemoteLoginWindow *login_window);
+
+} GdmRemoteLoginWindowClass;
+
+GType               gdm_remote_login_window_get_type           (void);
+GtkWidget *         gdm_remote_login_window_new                (gboolean display_is_local);
+
+gboolean            gdm_remote_login_window_connect            (GdmRemoteLoginWindow *login_window,
+                                                                const char           *host);
+gboolean            gdm_remote_login_window_discconnect        (GdmRemoteLoginWindow *login_window);
+
+G_END_DECLS
+
+#endif /* __GDM_REMOTE_LOGIN_WINDOW_H */
diff --git a/gui/simple-greeter/gdm-scrollable-widget.c b/gui/simple-greeter/gdm-scrollable-widget.c
new file mode 100644
index 0000000..66d6fb3
--- /dev/null
+++ b/gui/simple-greeter/gdm-scrollable-widget.c
@@ -0,0 +1,909 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ * Written by: Ray Strode <rstrode redhat com>
+ *
+ * Parts taken from gtkscrolledwindow.c in the GTK+ toolkit.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include "gdm-scrollable-widget.h"
+#include "gdm-timer.h"
+
+#define GDM_SCROLLABLE_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_SCROLLABLE_WIDGET, 
GdmScrollableWidgetPrivate))
+
+enum
+{
+        SCROLL_CHILD,
+        MOVE_FOCUS_OUT,
+        NUMBER_OF_SIGNALS
+};
+
+typedef struct GdmScrollableWidgetAnimation GdmScrollableWidgetAnimation;
+
+struct GdmScrollableWidgetPrivate
+{
+        GtkWidget *scrollbar;
+
+        GdmScrollableWidgetAnimation *animation;
+        GtkWidget *invisible_event_sink;
+        guint      key_press_signal_id;
+        guint      key_release_signal_id;
+
+        int        forced_height;
+
+        GQueue    *key_event_queue;
+};
+
+struct GdmScrollableWidgetAnimation
+{
+        GtkWidget *widget;
+        GdmTimer  *timer;
+        int        start_height;
+        int        desired_height;
+        GdmScrollableWidgetSlideStepFunc step_func;
+        gpointer   step_func_user_data;
+        GdmScrollableWidgetSlideDoneFunc done_func;
+        gpointer   done_func_user_data;
+};
+
+static void     gdm_scrollable_widget_class_init  (GdmScrollableWidgetClass *klass);
+static void     gdm_scrollable_widget_init        (GdmScrollableWidget      *clock_widget);
+static void     gdm_scrollable_widget_finalize    (GObject             *object);
+
+static guint signals[NUMBER_OF_SIGNALS] = { 0 };
+
+G_DEFINE_TYPE (GdmScrollableWidget, gdm_scrollable_widget, GTK_TYPE_BIN)
+
+static GdmScrollableWidgetAnimation *
+gdm_scrollable_widget_animation_new (GtkWidget *widget,
+                                     int        start_height,
+                                     int        desired_height,
+                                     GdmScrollableWidgetSlideStepFunc step_func,
+                                     gpointer   step_func_user_data,
+                                     GdmScrollableWidgetSlideDoneFunc done_func,
+                                     gpointer   done_func_user_data)
+{
+        GdmScrollableWidgetAnimation *animation;
+
+        animation = g_slice_new (GdmScrollableWidgetAnimation);
+
+        animation->widget = widget;
+        animation->timer = gdm_timer_new ();
+        animation->start_height = start_height;
+        animation->desired_height = desired_height;
+        animation->step_func = step_func;
+        animation->step_func_user_data = step_func_user_data;
+        animation->done_func = done_func;
+        animation->done_func_user_data = done_func_user_data;
+
+        return animation;
+}
+
+static void
+gdm_scrollable_widget_animation_free (GdmScrollableWidgetAnimation *animation)
+{
+        g_object_unref (animation->timer);
+        animation->timer = NULL;
+        g_slice_free (GdmScrollableWidgetAnimation, animation);
+}
+
+static void
+on_animation_tick (GdmScrollableWidgetAnimation *animation,
+                   double                        progress)
+{
+        GdmScrollableWidget *scrollable_widget;
+        int progress_in_pixels;
+        int height;
+
+        scrollable_widget = GDM_SCROLLABLE_WIDGET (animation->widget);
+
+        progress_in_pixels = progress * (animation->start_height - animation->desired_height);
+
+        height = animation->start_height - progress_in_pixels;
+        scrollable_widget->priv->forced_height = height;
+
+        gtk_widget_queue_resize (animation->widget);
+
+        if (animation->step_func != NULL) {
+                GdmTimer *timer;
+                GtkStyleContext *context;
+                GtkStateFlags state;
+                GtkBorder padding, border;
+
+                height = animation->desired_height;
+
+                context = gtk_widget_get_style_context (animation->widget);
+                state = gtk_widget_get_state_flags (animation->widget);
+
+                gtk_style_context_get_padding (context, state, &padding);
+                gtk_style_context_get_border (context, state, &border);
+
+                height -= padding.top + padding.bottom;
+                height -= border.top + border.bottom;
+
+                timer = g_object_ref (animation->timer);
+                animation->step_func (GDM_SCROLLABLE_WIDGET (animation->widget),
+                                      progress,
+                                      &height,
+                                      animation->step_func_user_data);
+
+                if (gdm_timer_is_started (timer)) {
+                        height += padding.top + padding.bottom;
+                        height += border.top + border.bottom;
+
+                        animation->desired_height = height;
+                }
+                g_object_unref (timer);
+        }
+}
+
+static gboolean
+on_key_event (GdmScrollableWidget *scrollable_widget,
+              GdkEventKey         *key_event)
+{
+        g_queue_push_tail (scrollable_widget->priv->key_event_queue,
+                           gdk_event_copy ((GdkEvent *)key_event));
+        return FALSE;
+}
+
+static gboolean
+gdm_scrollable_redirect_input_to_event_sink (GdmScrollableWidget *scrollable_widget)
+{
+        GdkGrabStatus status;
+
+        status = gdk_pointer_grab (gtk_widget_get_window (scrollable_widget->priv->invisible_event_sink),
+                          FALSE, 0, NULL, NULL, GDK_CURRENT_TIME);
+        if (status != GDK_GRAB_SUCCESS) {
+                return FALSE;
+        }
+
+        status = gdk_keyboard_grab (gtk_widget_get_window (scrollable_widget->priv->invisible_event_sink),
+                           FALSE, GDK_CURRENT_TIME);
+        if (status != GDK_GRAB_SUCCESS) {
+                gdk_pointer_ungrab (GDK_CURRENT_TIME);
+                return FALSE;
+        }
+
+        scrollable_widget->priv->key_press_signal_id =
+            g_signal_connect_swapped (scrollable_widget->priv->invisible_event_sink,
+                                      "key-press-event", G_CALLBACK (on_key_event),
+                                      scrollable_widget);
+
+        scrollable_widget->priv->key_release_signal_id =
+            g_signal_connect_swapped (scrollable_widget->priv->invisible_event_sink,
+                                      "key-release-event", G_CALLBACK (on_key_event),
+                                      scrollable_widget);
+
+        return TRUE;
+}
+
+static void
+gdm_scrollable_unredirect_input (GdmScrollableWidget *scrollable_widget)
+{
+        g_signal_handler_disconnect (scrollable_widget->priv->invisible_event_sink,
+                                     scrollable_widget->priv->key_press_signal_id);
+        scrollable_widget->priv->key_press_signal_id = 0;
+
+        g_signal_handler_disconnect (scrollable_widget->priv->invisible_event_sink,
+                                     scrollable_widget->priv->key_release_signal_id);
+        scrollable_widget->priv->key_release_signal_id = 0;
+        gdk_keyboard_ungrab (GDK_CURRENT_TIME);
+        gdk_pointer_ungrab (GDK_CURRENT_TIME);
+}
+
+static void
+on_animation_stop (GdmScrollableWidgetAnimation *animation)
+{
+        GdmScrollableWidget *widget;
+
+        widget = GDM_SCROLLABLE_WIDGET (animation->widget);
+
+        if (animation->done_func != NULL) {
+                animation->done_func (widget, animation->done_func_user_data);
+        }
+
+        gdm_scrollable_widget_animation_free (widget->priv->animation);
+        widget->priv->animation = NULL;
+
+        gdm_scrollable_unredirect_input (widget);
+}
+
+static void
+gdm_scrollable_widget_animation_start (GdmScrollableWidgetAnimation *animation)
+{
+        g_signal_connect_swapped (G_OBJECT (animation->timer), "tick",
+                                  G_CALLBACK (on_animation_tick),
+                                  animation);
+        g_signal_connect_swapped (G_OBJECT (animation->timer), "stop",
+                                  G_CALLBACK (on_animation_stop),
+                                  animation);
+        gdm_timer_start (animation->timer, .10);
+}
+
+static void
+gdm_scrollable_widget_animation_stop (GdmScrollableWidgetAnimation *animation)
+{
+        gdm_timer_stop (animation->timer);
+}
+
+static gboolean
+gdm_scrollable_widget_needs_scrollbar (GdmScrollableWidget *widget)
+{
+        GtkWidget *child;
+        gboolean needs_scrollbar;
+
+        if (widget->priv->scrollbar == NULL) {
+                return FALSE;
+        }
+
+        if (widget->priv->animation != NULL) {
+                return FALSE;
+        }
+
+        child = gtk_bin_get_child (GTK_BIN (widget));
+        if (child != NULL && GTK_IS_SCROLLABLE (child)) {
+                GtkStyleContext *context;
+                GtkStateFlags state;
+                GtkBorder padding, border;
+                int available_height;
+                int child_scrolled_height;
+
+                context = gtk_widget_get_style_context (GTK_WIDGET (widget));
+                state = gtk_widget_get_state_flags (GTK_WIDGET (widget));
+
+                gtk_style_context_get_padding (context, state, &padding);
+                gtk_style_context_get_border (context, state, &border);
+
+                available_height = gtk_widget_get_allocated_height (GTK_WIDGET (widget));
+                available_height -= padding.top + padding.bottom;
+                available_height -= border.top + border.bottom;
+
+                gtk_widget_get_preferred_height (child, NULL, &child_scrolled_height);
+                needs_scrollbar = child_scrolled_height > available_height;
+        } else {
+                needs_scrollbar = FALSE;
+        }
+
+        return needs_scrollbar;
+}
+
+static void
+gdm_scrollable_widget_get_preferred_size (GtkWidget      *widget,
+                                          GtkOrientation  orientation,
+                                          gint           *minimum_size,
+                                          gint           *natural_size)
+{
+        GdmScrollableWidget *scrollable_widget;
+        GtkStyleContext *context;
+        GtkStateFlags state;
+        GtkBorder padding, border;
+        GtkRequisition scrollbar_requisition;
+        GtkRequisition minimum_req, natural_req;
+        GtkWidget *child;
+        int min_child_size, nat_child_size;
+
+        context = gtk_widget_get_style_context (widget);
+        state = gtk_widget_get_state_flags (widget);
+
+        gtk_style_context_get_padding (context, state, &padding);
+        gtk_style_context_get_border (context, state, &border);
+
+        scrollable_widget = GDM_SCROLLABLE_WIDGET (widget);
+
+        minimum_req.width = padding.left + padding.right;
+        minimum_req.width += border.left + border.right;
+        minimum_req.height = padding.top + padding.bottom;
+        minimum_req.height += border.top + border.bottom;
+
+        natural_req.width = padding.left + padding.right;
+        natural_req.width += border.left + border.right;
+        natural_req.height = padding.top + padding.bottom;
+        natural_req.height += border.top + border.bottom;
+
+        if (orientation == GTK_ORIENTATION_VERTICAL
+            && scrollable_widget->priv->forced_height >= 0) {
+                minimum_req.height += scrollable_widget->priv->forced_height;
+                natural_req.height += scrollable_widget->priv->forced_height;
+        } else {
+                child = gtk_bin_get_child (GTK_BIN (widget));
+
+                gtk_widget_get_preferred_size (scrollable_widget->priv->scrollbar,
+                                               &scrollbar_requisition,
+                                               NULL);
+
+                if (child && gtk_widget_get_visible (child)) {
+                        if (orientation == GTK_ORIENTATION_HORIZONTAL) {
+                                gtk_widget_get_preferred_width (child,
+                                                                &min_child_size,
+                                                                &nat_child_size);
+                                minimum_req.width += min_child_size;
+                                natural_req.width += nat_child_size;
+                        } else {
+                                gtk_widget_get_preferred_height (child,
+                                                                 &min_child_size,
+                                                                 &nat_child_size);
+
+                                natural_req.height += nat_child_size;
+                        }
+                }
+
+                if (gdm_scrollable_widget_needs_scrollbar (scrollable_widget)) {
+                        minimum_req.height = MAX (minimum_req.height,
+                                                  scrollbar_requisition.height);
+                        minimum_req.width += scrollbar_requisition.width;
+                        natural_req.height = MAX (natural_req.height,
+                                                  scrollbar_requisition.height);
+                        natural_req.width += scrollbar_requisition.width;
+                }
+        }
+
+        if (orientation == GTK_ORIENTATION_HORIZONTAL) {
+                if (minimum_size)
+                        *minimum_size = minimum_req.width;
+                if (natural_size)
+                        *natural_size = natural_req.width;
+        } else {
+                if (minimum_size)
+                        *minimum_size = minimum_req.height;
+                if (natural_size)
+                        *natural_size = natural_req.height;
+        }
+}
+
+static void
+gdm_scrollable_widget_get_preferred_width (GtkWidget *widget,
+                                           gint      *minimum_size,
+                                           gint      *natural_size)
+{
+        gdm_scrollable_widget_get_preferred_size (widget, GTK_ORIENTATION_HORIZONTAL, minimum_size, 
natural_size);
+}
+
+static void
+gdm_scrollable_widget_get_preferred_height (GtkWidget *widget,
+                                            gint      *minimum_size,
+                                            gint      *natural_size)
+{
+        gdm_scrollable_widget_get_preferred_size (widget, GTK_ORIENTATION_VERTICAL, minimum_size, 
natural_size);
+}
+
+static void
+gdm_scrollable_widget_size_allocate (GtkWidget     *widget,
+                                     GtkAllocation *allocation)
+{
+        GdmScrollableWidget *scrollable_widget;
+        GtkAllocation        scrollbar_allocation;
+        GtkAllocation        child_allocation;
+        gboolean             has_child;
+        gboolean             needs_scrollbar;
+        gboolean             is_flipped;
+        GtkWidget           *child;
+        GtkStyleContext     *context;
+        GtkStateFlags        state;
+        GtkBorder            padding, border;
+
+        scrollable_widget = GDM_SCROLLABLE_WIDGET (widget);
+        context = gtk_widget_get_style_context (widget);
+        state = gtk_widget_get_state_flags (widget);
+
+        gtk_style_context_get_padding (context, state, &padding);
+        gtk_style_context_get_border (context, state, &border);
+
+        gtk_widget_set_allocation (widget, allocation);
+
+        child = gtk_bin_get_child (GTK_BIN (widget));
+        has_child = child && gtk_widget_get_visible (child);
+        needs_scrollbar = gdm_scrollable_widget_needs_scrollbar (scrollable_widget);
+        is_flipped = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
+
+        if (needs_scrollbar) {
+                gtk_widget_show (scrollable_widget->priv->scrollbar);
+
+                gtk_widget_get_preferred_width (scrollable_widget->priv->scrollbar, NULL, 
&scrollbar_allocation.width);
+
+                if (!is_flipped) {
+                        scrollbar_allocation.x = allocation->x + allocation->width;
+                        scrollbar_allocation.x -= scrollbar_allocation.width - padding.right;
+                } else {
+                        scrollbar_allocation.x = allocation->x + padding.right + border.right;
+                }
+
+                scrollbar_allocation.height = allocation->height;
+
+                scrollbar_allocation.y = allocation->y + padding.top;
+
+                gtk_widget_size_allocate (scrollable_widget->priv->scrollbar,
+                                          &scrollbar_allocation);
+        } else {
+                gtk_widget_hide (scrollable_widget->priv->scrollbar);
+        }
+
+        if (has_child) {
+                child_allocation.width = allocation->width;
+                child_allocation.width = MAX (child_allocation.width - padding.left, 1);
+                child_allocation.width = MAX (child_allocation.width - padding.right, 1);
+                child_allocation.width = MAX (child_allocation.width - border.left, 1);
+                child_allocation.width = MAX (child_allocation.width - border.right, 1);
+
+                if (needs_scrollbar) {
+                        child_allocation.width = MAX (child_allocation.width - scrollbar_allocation.width, 
1);
+                }
+
+                if (!is_flipped) {
+                        child_allocation.x = allocation->x;
+                        child_allocation.x += padding.left;
+                        child_allocation.x += border.left;
+                } else {
+                        child_allocation.x = allocation->x + allocation->width;
+                        child_allocation.x -= child_allocation.width;
+                        child_allocation.x -= padding.left;
+                        child_allocation.x -= border.left;
+                }
+
+                child_allocation.height = allocation->height;
+                child_allocation.height = MAX (child_allocation.height - padding.top, 1);
+                child_allocation.height = MAX (child_allocation.height - border.top, 1);
+                child_allocation.height = MAX (child_allocation.height - padding.bottom, 1);
+                child_allocation.height = MAX (child_allocation.height - border.bottom, 1);
+
+                child_allocation.y = allocation->y;
+                child_allocation.y += padding.top;
+                child_allocation.y += border.top;
+
+                gtk_widget_size_allocate (child,
+                                          &child_allocation);
+        }
+}
+
+static void
+gdm_scrollable_widget_add (GtkContainer *container,
+                           GtkWidget    *child)
+{
+        GtkAdjustment *adjustment;
+
+        GTK_CONTAINER_CLASS (gdm_scrollable_widget_parent_class)->add (container, child);
+
+        adjustment = gtk_range_get_adjustment (GTK_RANGE (GDM_SCROLLABLE_WIDGET 
(container)->priv->scrollbar));
+
+        g_signal_connect_swapped (adjustment, "changed",
+                                  G_CALLBACK (gtk_widget_queue_resize),
+                                  container);
+
+        if (GTK_IS_SCROLLABLE (child))
+                g_object_set (child, "hadjustment", NULL, "vadjustment", adjustment, NULL);
+        else
+                g_warning ("gdm_scrollable_widget_add(): cannot add non scrollable widget");
+}
+
+static void
+gdm_scrollable_widget_remove (GtkContainer *container,
+                              GtkWidget    *child)
+{
+        g_object_set (child, "hadjustment", NULL, "vadjustment", NULL, NULL);
+
+        GTK_CONTAINER_CLASS (gdm_scrollable_widget_parent_class)->remove (container, child);
+}
+
+static void
+gdm_scrollable_widget_forall (GtkContainer *container,
+                              gboolean      include_internals,
+                              GtkCallback   callback,
+                              gpointer      callback_data)
+{
+
+        GdmScrollableWidget *scrollable_widget;
+
+        scrollable_widget = GDM_SCROLLABLE_WIDGET (container);
+
+        GTK_CONTAINER_CLASS (gdm_scrollable_widget_parent_class)->forall (container,
+                                                                          include_internals,
+                                                                          callback,
+                                                                          callback_data);
+
+        if (!include_internals) {
+                return;
+        }
+
+        if (scrollable_widget->priv->scrollbar != NULL) {
+                callback (scrollable_widget->priv->scrollbar, callback_data);
+        }
+}
+
+static void
+gdm_scrollable_widget_destroy (GtkWidget *object)
+{
+        GdmScrollableWidget *scrollable_widget;
+
+        scrollable_widget = GDM_SCROLLABLE_WIDGET (object);
+
+        gtk_widget_unparent (scrollable_widget->priv->scrollbar);
+        gtk_widget_destroy (scrollable_widget->priv->scrollbar);
+
+        GTK_WIDGET_CLASS (gdm_scrollable_widget_parent_class)->destroy (object);
+}
+
+static void
+gdm_scrollable_widget_finalize (GObject *object)
+{
+        GdmScrollableWidget *scrollable_widget;
+
+        scrollable_widget = GDM_SCROLLABLE_WIDGET (object);
+
+        g_queue_free (scrollable_widget->priv->key_event_queue);
+
+        G_OBJECT_CLASS (gdm_scrollable_widget_parent_class)->finalize (object);
+}
+
+static gboolean
+gdm_scrollable_widget_draw (GtkWidget *widget,
+                            cairo_t   *cr)
+{
+        GdmScrollableWidget *scrollable_widget;
+        int                  x;
+        int                  y;
+        int                  width;
+        int                  height;
+        gboolean             is_flipped;
+        GtkStyleContext     *context;
+        GtkStateFlags        state;
+        GtkBorder            padding, border;
+        GtkAllocation widget_allocation;
+
+        context = gtk_widget_get_style_context (widget);
+        state = gtk_widget_get_state_flags (widget);
+        is_flipped = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
+
+        gtk_style_context_get_padding (context, state, &padding);
+        gtk_style_context_get_border (context, state, &border);
+
+        gtk_widget_get_allocation (widget, &widget_allocation);
+
+        scrollable_widget = GDM_SCROLLABLE_WIDGET (widget);
+
+        if (!gtk_widget_is_drawable (widget)) {
+                return FALSE;
+        }
+
+        x = 0;
+        x += padding.left;
+
+        width = widget_allocation.width;
+        width -= padding.left + padding.right;
+
+        if (gdm_scrollable_widget_needs_scrollbar (scrollable_widget)) {
+                GtkAllocation scrollbar_allocation;
+                gtk_widget_get_allocation (scrollable_widget->priv->scrollbar, &scrollbar_allocation);
+                width -= scrollbar_allocation.width;
+
+                if (is_flipped) {
+                        x += scrollbar_allocation.width;
+                }
+        }
+
+        y = 0;
+        y += padding.top;
+
+        height = widget_allocation.height;
+        height -= padding.top + padding.bottom;
+
+        if (width > 0 && height > 0) {
+                gtk_render_frame (context, cr,
+                                  x, y, width, height);
+        }
+
+        return GTK_WIDGET_CLASS (gdm_scrollable_widget_parent_class)->draw (widget, cr);
+}
+
+static gboolean
+gdm_scrollable_widget_scroll_event (GtkWidget      *widget,
+                                    GdkEventScroll *event)
+{
+        if (event->direction != GDK_SCROLL_UP && event->direction != GDK_SCROLL_DOWN) {
+                return FALSE;
+        }
+
+        if (!gtk_widget_get_visible (GTK_WIDGET (widget))) {
+                return FALSE;
+        }
+
+        return gtk_widget_event (GDM_SCROLLABLE_WIDGET (widget)->priv->scrollbar,
+                                 (GdkEvent *) event);
+}
+
+static void
+add_scroll_binding (GtkBindingSet  *binding_set,
+                    guint           keyval,
+                    GdkModifierType mask,
+                    GtkScrollType   scroll)
+{
+        guint keypad_keyval = keyval - GDK_KEY_Left + GDK_KEY_KP_Left;
+
+        gtk_binding_entry_add_signal (binding_set, keyval, mask,
+                                      "scroll-child", 1,
+                                      GTK_TYPE_SCROLL_TYPE, scroll);
+        gtk_binding_entry_add_signal (binding_set, keypad_keyval, mask,
+                                      "scroll-child", 1,
+                                      GTK_TYPE_SCROLL_TYPE, scroll);
+}
+
+static void
+add_tab_bindings (GtkBindingSet    *binding_set,
+                  GdkModifierType   modifiers,
+                  GtkDirectionType  direction)
+{
+        gtk_binding_entry_add_signal (binding_set, GDK_KEY_Tab, modifiers,
+                                      "move-focus-out", 1,
+                                      GTK_TYPE_DIRECTION_TYPE, direction);
+        gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Tab, modifiers,
+                                      "move-focus-out", 1,
+                                      GTK_TYPE_DIRECTION_TYPE, direction);
+}
+
+static void
+gdm_scrollable_widget_class_install_bindings (GdmScrollableWidgetClass *klass)
+{
+        GtkBindingSet *binding_set;
+
+        binding_set = gtk_binding_set_by_class (klass);
+
+        add_scroll_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK, GTK_SCROLL_STEP_BACKWARD);
+        add_scroll_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK, GTK_SCROLL_STEP_FORWARD);
+
+        add_scroll_binding (binding_set, GDK_KEY_Page_Up, 0, GTK_SCROLL_PAGE_BACKWARD);
+        add_scroll_binding (binding_set, GDK_KEY_Page_Down, 0, GTK_SCROLL_PAGE_FORWARD);
+
+        add_scroll_binding (binding_set, GDK_KEY_Home, 0, GTK_SCROLL_START);
+        add_scroll_binding (binding_set, GDK_KEY_End, 0, GTK_SCROLL_END);
+
+        add_tab_bindings (binding_set, GDK_CONTROL_MASK, GTK_DIR_TAB_FORWARD);
+        add_tab_bindings (binding_set, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_DIR_TAB_BACKWARD);
+}
+
+static void
+gdm_scrollable_widget_class_init (GdmScrollableWidgetClass *klass)
+{
+        GObjectClass             *object_class;
+        GtkWidgetClass           *widget_class;
+        GtkContainerClass        *container_class;
+        GdmScrollableWidgetClass *scrollable_widget_class;
+
+        object_class = G_OBJECT_CLASS (klass);
+        widget_class = GTK_WIDGET_CLASS (klass);
+        container_class = GTK_CONTAINER_CLASS (klass);
+        scrollable_widget_class = GDM_SCROLLABLE_WIDGET_CLASS (klass);
+
+        object_class->finalize = gdm_scrollable_widget_finalize;
+
+        widget_class->destroy = gdm_scrollable_widget_destroy;
+
+        widget_class->get_preferred_width = gdm_scrollable_widget_get_preferred_width;
+        widget_class->get_preferred_height = gdm_scrollable_widget_get_preferred_height;
+        widget_class->size_allocate = gdm_scrollable_widget_size_allocate;
+        widget_class->draw = gdm_scrollable_widget_draw;
+        widget_class->scroll_event = gdm_scrollable_widget_scroll_event;
+
+        container_class->add = gdm_scrollable_widget_add;
+        container_class->remove = gdm_scrollable_widget_remove;
+        container_class->forall = gdm_scrollable_widget_forall;
+        gtk_container_class_handle_border_width (container_class);
+
+        signals[SCROLL_CHILD] =
+          g_signal_new ("scroll-child",
+                        G_TYPE_FROM_CLASS (object_class),
+                        G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                        G_STRUCT_OFFSET (GtkScrolledWindowClass, scroll_child),
+                        NULL, NULL,
+                        g_cclosure_marshal_VOID__ENUM,
+                        G_TYPE_BOOLEAN, 1,
+                        GTK_TYPE_SCROLL_TYPE);
+        signals[MOVE_FOCUS_OUT] =
+          g_signal_new ("move-focus-out",
+                        G_TYPE_FROM_CLASS (object_class),
+                        G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                        G_STRUCT_OFFSET (GtkScrolledWindowClass, move_focus_out),
+                        NULL, NULL,
+                        g_cclosure_marshal_VOID__ENUM,
+                        G_TYPE_NONE, 1,
+                        GTK_TYPE_DIRECTION_TYPE);
+        gdm_scrollable_widget_class_install_bindings (klass);
+
+        g_type_class_add_private (klass, sizeof (GdmScrollableWidgetPrivate));
+}
+
+static void
+gdm_scrollable_widget_add_scrollbar (GdmScrollableWidget *widget)
+{
+        gtk_widget_push_composite_child ();
+        widget->priv->scrollbar = gtk_vscrollbar_new (NULL);
+        g_object_set (widget->priv->scrollbar, "expand", TRUE, NULL);
+        gtk_widget_set_composite_name (widget->priv->scrollbar, "scrollbar");
+        gtk_widget_pop_composite_child ();
+        gtk_widget_set_parent (widget->priv->scrollbar, GTK_WIDGET (widget));
+        g_object_ref (widget->priv->scrollbar);
+}
+
+static void
+gdm_scrollable_widget_add_invisible_event_sink (GdmScrollableWidget *widget)
+{
+        widget->priv->invisible_event_sink =
+            gtk_invisible_new_for_screen (gtk_widget_get_screen (GTK_WIDGET (widget)));
+        gtk_widget_show (widget->priv->invisible_event_sink);
+
+        widget->priv->key_event_queue = g_queue_new ();
+}
+
+static void
+gdm_scrollable_widget_adopt_style_from_scrolled_window_class (GdmScrollableWidget *widget)
+{
+        GtkStyleContext *context;
+        GtkWidgetPath   *path;
+
+        context = gtk_widget_get_style_context (GTK_WIDGET (widget));
+
+        gtk_style_context_add_class (context, GTK_STYLE_CLASS_FRAME);
+
+        path = gtk_widget_path_new ();
+        gtk_widget_path_append_type (path, GTK_TYPE_SCROLLED_WINDOW);
+        gtk_style_context_set_path (context, path);
+        gtk_widget_path_free (path);
+}
+
+static void
+gdm_scrollable_widget_init (GdmScrollableWidget *widget)
+{
+        widget->priv = GDM_SCROLLABLE_WIDGET_GET_PRIVATE (widget);
+
+        widget->priv->forced_height = -1;
+
+        gdm_scrollable_widget_add_scrollbar (widget);
+        gdm_scrollable_widget_add_invisible_event_sink (widget);
+
+        gdm_scrollable_widget_adopt_style_from_scrolled_window_class (widget);
+        g_signal_connect (G_OBJECT (widget),
+                          "style-updated",
+                          G_CALLBACK (gdm_scrollable_widget_adopt_style_from_scrolled_window_class),
+                          NULL);
+}
+
+GtkWidget *
+gdm_scrollable_widget_new (void)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_SCROLLABLE_WIDGET, NULL);
+
+        return GTK_WIDGET (object);
+}
+
+static gboolean
+gdm_scrollable_widget_animations_are_disabled (GdmScrollableWidget *scrollable_widget)
+{
+        GtkSettings *settings;
+        gboolean     animations_are_enabled;
+
+        settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (scrollable_widget)));
+        g_object_get (settings, "gtk-enable-animations", &animations_are_enabled, NULL);
+
+        return animations_are_enabled == FALSE;
+}
+
+void
+gdm_scrollable_widget_stop_sliding (GdmScrollableWidget *scrollable_widget)
+{
+        g_return_if_fail (GDM_IS_SCROLLABLE_WIDGET (scrollable_widget));
+
+        if (scrollable_widget->priv->animation != NULL) {
+                gdm_scrollable_widget_animation_stop (scrollable_widget->priv->animation);
+        }
+
+        g_assert (scrollable_widget->priv->animation == NULL);
+}
+
+void
+gdm_scrollable_widget_slide_to_height (GdmScrollableWidget *scrollable_widget,
+                                       int                  height,
+                                       GdmScrollableWidgetSlideStepFunc step_func,
+                                       gpointer             step_user_data,
+                                       GdmScrollableWidgetSlideDoneFunc done_func,
+                                       gpointer             done_user_data)
+{
+        GtkWidget *widget;
+        gboolean   input_redirected;
+        GtkAllocation widget_allocation;
+        GtkStyleContext *context;
+        GtkStateFlags state;
+        GtkBorder padding, border;
+
+        g_return_if_fail (GDM_IS_SCROLLABLE_WIDGET (scrollable_widget));
+        widget = GTK_WIDGET (scrollable_widget);
+
+        gdm_scrollable_widget_stop_sliding (scrollable_widget);
+
+        input_redirected = gdm_scrollable_redirect_input_to_event_sink (scrollable_widget);
+
+        if (!input_redirected || gdm_scrollable_widget_animations_are_disabled (scrollable_widget)) {
+                scrollable_widget->priv->forced_height = height;
+
+                if (step_func != NULL) {
+                        step_func (scrollable_widget, 0.0, &height, step_user_data);
+                }
+
+                if (done_func != NULL) {
+                        done_func (scrollable_widget, done_user_data);
+                }
+
+                if (input_redirected) {
+                        gdm_scrollable_unredirect_input (scrollable_widget);
+                }
+
+                return;
+        }
+
+        context = gtk_widget_get_style_context (widget);
+        state = gtk_widget_get_state_flags (widget);
+
+        gtk_style_context_get_padding (context, state, &padding);
+        gtk_style_context_get_border (context, state, &border);
+
+        height += padding.top + padding.bottom;
+        height += border.top + border.bottom;
+
+        gtk_widget_get_allocation (widget, &widget_allocation);
+
+        scrollable_widget->priv->animation =
+            gdm_scrollable_widget_animation_new (widget,
+                                                 widget_allocation.height,
+                                                 height, step_func, step_user_data,
+                                                 done_func, done_user_data);
+
+        gdm_scrollable_widget_animation_start (scrollable_widget->priv->animation);
+}
+
+gboolean
+gdm_scrollable_widget_has_queued_key_events (GdmScrollableWidget *widget)
+{
+        g_return_val_if_fail (GDM_IS_SCROLLABLE_WIDGET (widget), FALSE);
+
+        return !g_queue_is_empty (widget->priv->key_event_queue);
+}
+
+void
+gdm_scrollable_widget_replay_queued_key_events (GdmScrollableWidget *widget)
+{
+        GtkWidget *toplevel;
+        GdkEvent  *event;
+
+        toplevel = gtk_widget_get_toplevel (GTK_WIDGET (widget));
+
+        while ((event = g_queue_pop_head (widget->priv->key_event_queue)) != NULL) {
+                gtk_propagate_event (toplevel, event);
+        }
+}
diff --git a/gui/simple-greeter/gdm-scrollable-widget.h b/gui/simple-greeter/gdm-scrollable-widget.h
new file mode 100644
index 0000000..5729924
--- /dev/null
+++ b/gui/simple-greeter/gdm-scrollable-widget.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *  Written by: Ray Strode <rstrode redhat com>
+ */
+
+#ifndef __GDM_SCROLLABLE_WIDGET_H
+#define __GDM_SCROLLABLE_WIDGET_H
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_SCROLLABLE_WIDGET         (gdm_scrollable_widget_get_type ())
+#define GDM_SCROLLABLE_WIDGET(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_SCROLLABLE_WIDGET, 
GdmScrollableWidget))
+#define GDM_SCROLLABLE_WIDGET_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_SCROLLABLE_WIDGET, 
GdmScrollableWidgetClass))
+#define GDM_IS_SCROLLABLE_WIDGET(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_SCROLLABLE_WIDGET))
+#define GDM_IS_SCROLLABLE_WIDGET_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_SCROLLABLE_WIDGET))
+#define GDM_SCROLLABLE_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_SCROLLABLE_WIDGET, 
GdmScrollableWidgetClass))
+
+typedef struct GdmScrollableWidgetClass GdmScrollableWidgetClass;
+typedef struct GdmScrollableWidget GdmScrollableWidget;
+typedef struct GdmScrollableWidgetPrivate GdmScrollableWidgetPrivate;
+typedef void (* GdmScrollableWidgetSlideStepFunc) (GdmScrollableWidget *scrollable_widget,
+                                                   double               progress,
+                                                   int                 *new_height,
+                                                   gpointer            *user_data);
+typedef void (* GdmScrollableWidgetSlideDoneFunc) (GdmScrollableWidget *scrollable_widget,
+                                                   gpointer            *user_data);
+
+struct GdmScrollableWidget
+{
+        GtkBin                      parent;
+        GdmScrollableWidgetPrivate *priv;
+};
+
+struct GdmScrollableWidgetClass
+{
+        GtkBinClass parent_class;
+
+        void (* scroll_child) (GdmScrollableWidget *widget, GtkScrollType type);
+        void (* move_focus_out) (GdmScrollableWidget *widget, GtkDirectionType type);
+
+};
+
+GType                  gdm_scrollable_widget_get_type               (void);
+GtkWidget *            gdm_scrollable_widget_new                    (void);
+void                   gdm_scrollable_widget_stop_sliding           (GdmScrollableWidget *widget);
+void                   gdm_scrollable_widget_slide_to_height        (GdmScrollableWidget *widget,
+                                                                     int                  height,
+                                                                     GdmScrollableWidgetSlideStepFunc 
step_func,
+                                                                     gpointer             step_user_data,
+                                                                     GdmScrollableWidgetSlideDoneFunc 
done_func,
+                                                                     gpointer             data);
+gboolean               gdm_scrollable_widget_has_queued_key_events (GdmScrollableWidget *widget);
+void                   gdm_scrollable_widget_replay_queued_key_events (GdmScrollableWidget *widget);
+#endif /* __GDM_SCROLLABLE_WIDGET_H */
diff --git a/gui/simple-greeter/gdm-session-option-widget.c b/gui/simple-greeter/gdm-session-option-widget.c
new file mode 100644
index 0000000..f37da23
--- /dev/null
+++ b/gui/simple-greeter/gdm-session-option-widget.c
@@ -0,0 +1,192 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Written by: William Jon McCann <mccann jhu edu>
+ *             Ray Strode <rstrode redhat com>
+ */
+
+#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 "gdm-session-option-widget.h"
+#include "gdm-sessions.h"
+
+#define GDM_SESSION_OPTION_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), 
GDM_TYPE_SESSION_OPTION_WIDGET, GdmSessionOptionWidgetPrivate))
+
+#define GDM_SESSION_OPTION_WIDGET_LAST_SESSION "__previous"
+
+struct GdmSessionOptionWidgetPrivate
+{
+        gpointer dummy;
+};
+
+enum {
+        PROP_0,
+};
+
+enum {
+        SESSION_ACTIVATED,
+        NUMBER_OF_SIGNALS
+};
+
+static guint signals [NUMBER_OF_SIGNALS] = { 0, };
+
+static void     gdm_session_option_widget_class_init  (GdmSessionOptionWidgetClass *klass);
+static void     gdm_session_option_widget_init        (GdmSessionOptionWidget      *session_option_widget);
+static void     gdm_session_option_widget_finalize    (GObject                     *object);
+
+G_DEFINE_TYPE (GdmSessionOptionWidget, gdm_session_option_widget, GDM_TYPE_OPTION_WIDGET)
+static void
+gdm_session_option_widget_activated (GdmOptionWidget *widget)
+{
+        char *active_item_id;
+
+        active_item_id = gdm_option_widget_get_active_item (GDM_OPTION_WIDGET (widget));
+        if (active_item_id == NULL) {
+                return;
+        }
+
+        g_signal_emit (G_OBJECT (widget), signals[SESSION_ACTIVATED], 0);
+}
+
+
+static void
+gdm_session_option_widget_class_init (GdmSessionOptionWidgetClass *klass)
+{
+        GObjectClass *object_class;
+        GdmOptionWidgetClass *option_widget_class;
+
+        object_class = G_OBJECT_CLASS (klass);
+        option_widget_class = GDM_OPTION_WIDGET_CLASS (klass);
+
+        object_class->finalize = gdm_session_option_widget_finalize;
+        option_widget_class->activated = gdm_session_option_widget_activated;
+
+        signals[SESSION_ACTIVATED] = g_signal_new ("session-activated",
+                                                   G_TYPE_FROM_CLASS (object_class),
+                                                   G_SIGNAL_RUN_FIRST,
+                                                   G_STRUCT_OFFSET (GdmSessionOptionWidgetClass, 
session_activated),
+                                                   NULL,
+                                                   NULL,
+                                                   g_cclosure_marshal_VOID__VOID,
+                                                   G_TYPE_NONE,
+                                                   0);
+
+        g_type_class_add_private (klass, sizeof (GdmSessionOptionWidgetPrivate));
+}
+
+static void
+add_available_sessions (GdmSessionOptionWidget *widget)
+{
+        char     **session_ids;
+        int        i;
+
+        session_ids = gdm_get_all_sessions ();
+
+        for (i = 0; session_ids[i] != NULL; i++) {
+                char *name;
+                char *comment;
+
+                if (!gdm_get_details_for_session (session_ids[i],
+                                                  &name, &comment)) {
+                        continue;
+                }
+
+                gdm_option_widget_add_item (GDM_OPTION_WIDGET (widget),
+                                            session_ids[i], name, comment,
+                                            GDM_OPTION_WIDGET_POSITION_MIDDLE);
+                g_free (name);
+                g_free (comment);
+        }
+
+        g_strfreev (session_ids);
+}
+
+static void
+gdm_session_option_widget_init (GdmSessionOptionWidget *widget)
+{
+        widget->priv = GDM_SESSION_OPTION_WIDGET_GET_PRIVATE (widget);
+
+        add_available_sessions (widget);
+}
+
+static void
+gdm_session_option_widget_finalize (GObject *object)
+{
+        GdmSessionOptionWidget *session_option_widget;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GDM_IS_SESSION_OPTION_WIDGET (object));
+
+        session_option_widget = GDM_SESSION_OPTION_WIDGET (object);
+
+        g_return_if_fail (session_option_widget->priv != NULL);
+
+        G_OBJECT_CLASS (gdm_session_option_widget_parent_class)->finalize (object);
+}
+
+GtkWidget *
+gdm_session_option_widget_new (void)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_SESSION_OPTION_WIDGET,
+                               "label-text", _("Session"),
+                               NULL);
+
+        return GTK_WIDGET (object);
+}
+
+char *
+gdm_session_option_widget_get_current_session (GdmSessionOptionWidget *widget)
+{
+        char *active_item_id;
+
+        active_item_id = gdm_option_widget_get_active_item (GDM_OPTION_WIDGET (widget));
+        if (active_item_id == NULL) {
+                return NULL;
+        }
+
+        return active_item_id;
+}
+
+void
+gdm_session_option_widget_set_current_session (GdmSessionOptionWidget *widget,
+                                               const char             *session)
+{
+        if (session == NULL) {
+                gdm_option_widget_set_active_item (GDM_OPTION_WIDGET (widget),
+                                                   GDM_SESSION_OPTION_WIDGET_LAST_SESSION);
+        } else if (gdm_option_widget_lookup_item (GDM_OPTION_WIDGET (widget), session,
+                                            NULL, NULL, NULL)) {
+                gdm_option_widget_set_active_item (GDM_OPTION_WIDGET (widget), session);
+        }
+}
diff --git a/gui/simple-greeter/gdm-session-option-widget.h b/gui/simple-greeter/gdm-session-option-widget.h
new file mode 100644
index 0000000..aaeacc4
--- /dev/null
+++ b/gui/simple-greeter/gdm-session-option-widget.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *  Written by: Ray Strode <rstrode redhat com>
+ */
+
+#ifndef __GDM_SESSION_OPTION_WIDGET_H
+#define __GDM_SESSION_OPTION_WIDGET_H
+
+#include <glib-object.h>
+
+#include "gdm-option-widget.h"
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_SESSION_OPTION_WIDGET         (gdm_session_option_widget_get_type ())
+#define GDM_SESSION_OPTION_WIDGET(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
GDM_TYPE_SESSION_OPTION_WIDGET, GdmSessionOptionWidget))
+#define GDM_SESSION_OPTION_WIDGET_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_SESSION_OPTION_WIDGET, 
GdmSessionOptionWidgetClass))
+#define GDM_IS_SESSION_OPTION_WIDGET(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
GDM_TYPE_SESSION_OPTION_WIDGET))
+#define GDM_IS_SESSION_OPTION_WIDGET_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), 
GDM_TYPE_SESSION_OPTION_WIDGET))
+#define GDM_SESSION_OPTION_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
GDM_TYPE_SESSION_OPTION_WIDGET, GdmSessionOptionWidgetClass))
+
+typedef struct GdmSessionOptionWidgetPrivate GdmSessionOptionWidgetPrivate;
+
+typedef struct
+{
+        GdmOptionWidget                parent;
+        GdmSessionOptionWidgetPrivate *priv;
+} GdmSessionOptionWidget;
+
+typedef struct
+{
+        GdmOptionWidgetClass              parent_class;
+
+        void (* session_activated)        (GdmSessionOptionWidget *widget);
+} GdmSessionOptionWidgetClass;
+
+GType                  gdm_session_option_widget_get_type               (void);
+GtkWidget *            gdm_session_option_widget_new                    (void);
+
+char *                 gdm_session_option_widget_get_current_session    (GdmSessionOptionWidget *widget);
+void                   gdm_session_option_widget_set_current_session    (GdmSessionOptionWidget *widget,
+                                                                         const char             *session);
+
+
+
+#endif /* __GDM_SESSION_OPTION_WIDGET_H */
diff --git a/gui/simple-greeter/gdm-sessions.c b/gui/simple-greeter/gdm-sessions.c
new file mode 100644
index 0000000..c8e9db6
--- /dev/null
+++ b/gui/simple-greeter/gdm-sessions.c
@@ -0,0 +1,265 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2008 Red Hat, Inc,
+ *           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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Written by : William Jon McCann <mccann jhu edu>
+ *              Ray Strode <rstrode redhat com>
+ */
+
+#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 "gdm-sessions.h"
+
+typedef struct _GdmSessionFile {
+        char    *id;
+        char    *path;
+        char    *translated_name;
+        char    *translated_comment;
+} GdmSessionFile;
+
+static GHashTable *gdm_available_sessions_map;
+
+static gboolean gdm_sessions_map_is_initialized = FALSE;
+
+/* adapted from gnome-menus desktop-entries.c */
+static gboolean
+key_file_is_relevant (GKeyFile     *key_file)
+{
+        GError    *error;
+        gboolean   no_display;
+        gboolean   hidden;
+        gboolean   tryexec_failed;
+        char      *tryexec;
+
+        error = NULL;
+        no_display = g_key_file_get_boolean (key_file,
+                                             G_KEY_FILE_DESKTOP_GROUP,
+                                             "NoDisplay",
+                                             &error);
+        if (error) {
+                no_display = FALSE;
+                g_error_free (error);
+        }
+
+        error = NULL;
+        hidden = g_key_file_get_boolean (key_file,
+                                         G_KEY_FILE_DESKTOP_GROUP,
+                                         "Hidden",
+                                         &error);
+        if (error) {
+                hidden = FALSE;
+                g_error_free (error);
+        }
+
+        tryexec_failed = FALSE;
+        tryexec = g_key_file_get_string (key_file,
+                                         G_KEY_FILE_DESKTOP_GROUP,
+                                         "TryExec",
+                                         NULL);
+        if (tryexec) {
+                char *path;
+
+                path = g_find_program_in_path (g_strstrip (tryexec));
+
+                tryexec_failed = (path == NULL);
+
+                g_free (path);
+                g_free (tryexec);
+        }
+
+        if (no_display || hidden || tryexec_failed) {
+                return FALSE;
+        }
+
+        return TRUE;
+}
+
+static void
+load_session_file (const char              *id,
+                   const char              *path)
+{
+        GKeyFile          *key_file;
+        GError            *error;
+        gboolean           res;
+        GdmSessionFile    *session;
+
+        key_file = g_key_file_new ();
+
+        error = NULL;
+        res = g_key_file_load_from_file (key_file, path, 0, &error);
+
+        if (!res) {
+                g_debug ("Failed to load \"%s\": %s\n", path, error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        if (! g_key_file_has_group (key_file, G_KEY_FILE_DESKTOP_GROUP)) {
+                goto out;
+        }
+
+        res = g_key_file_has_key (key_file, G_KEY_FILE_DESKTOP_GROUP, "Name", NULL);
+        if (! res) {
+                g_debug ("\"%s\" contains no \"Name\" key\n", path);
+                goto out;
+        }
+
+        if (!key_file_is_relevant (key_file)) {
+                g_debug ("\"%s\" is hidden or contains non-executable TryExec program\n", path);
+                goto out;
+        }
+
+        session = g_new0 (GdmSessionFile, 1);
+
+        session->id = g_strdup (id);
+        session->path = g_strdup (path);
+
+        session->translated_name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, "Name", 
NULL, NULL);
+        session->translated_comment = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, 
"Comment", NULL, NULL);
+
+        g_hash_table_insert (gdm_available_sessions_map,
+                             g_strdup (id),
+                             session);
+ out:
+        g_key_file_free (key_file);
+}
+
+static void
+collect_sessions_from_directory (const char *dirname)
+{
+        GDir       *dir;
+        const char *filename;
+
+        /* FIXME: add file monitor to directory */
+
+        dir = g_dir_open (dirname, 0, NULL);
+        if (dir == NULL) {
+                return;
+        }
+
+        while ((filename = g_dir_read_name (dir))) {
+                char *id;
+                char *full_path;
+
+                if (! g_str_has_suffix (filename, ".desktop")) {
+                        continue;
+                }
+                id = g_strndup (filename, strlen (filename) - strlen (".desktop"));
+
+                full_path = g_build_filename (dirname, filename, NULL);
+
+                load_session_file (id, full_path);
+
+                g_free (id);
+                g_free (full_path);
+        }
+
+        g_dir_close (dir);
+}
+
+static void
+collect_sessions (void)
+{
+        int         i;
+        const char *search_dirs[] = {
+                "/etc/X11/sessions/",
+                DMCONFDIR "/Sessions/",
+                DATADIR "/gdm/BuiltInSessions/",
+                DATADIR "/xsessions/",
+                NULL
+        };
+
+        if (gdm_available_sessions_map == NULL) {
+                gdm_available_sessions_map = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                                    g_free, g_free);
+        }
+
+        for (i = 0; search_dirs [i] != NULL; i++) {
+                collect_sessions_from_directory (search_dirs [i]);
+        }
+}
+
+char **
+gdm_get_all_sessions (void)
+{
+        GHashTableIter iter;
+        gpointer key, value;
+        GPtrArray *array;
+
+        if (!gdm_sessions_map_is_initialized) {
+                collect_sessions ();
+
+                gdm_sessions_map_is_initialized = TRUE;
+        }
+
+        array = g_ptr_array_new ();
+        g_hash_table_iter_init (&iter, gdm_available_sessions_map);
+        while (g_hash_table_iter_next (&iter, &key, &value)) {
+                GdmSessionFile *session;
+
+                session = (GdmSessionFile *) value;
+
+                g_ptr_array_add (array, g_strdup (session->id));
+        }
+        g_ptr_array_add (array, NULL);
+
+        return (char **) g_ptr_array_free (array, FALSE);
+}
+
+gboolean
+gdm_get_details_for_session (const char  *id,
+                             char       **name,
+                             char       **comment)
+{
+        GdmSessionFile *session;
+
+        if (!gdm_sessions_map_is_initialized) {
+                collect_sessions ();
+
+                gdm_sessions_map_is_initialized = TRUE;
+        }
+
+        session = (GdmSessionFile *) g_hash_table_lookup (gdm_available_sessions_map,
+                                                          id);
+
+        if (session == NULL) {
+                return FALSE;
+        }
+
+        if (name != NULL) {
+                *name = g_strdup (session->translated_name);
+        }
+
+        if (comment != NULL) {
+                *comment = g_strdup (session->translated_comment);
+        }
+
+        return TRUE;
+}
diff --git a/gui/simple-greeter/gdm-sessions.h b/gui/simple-greeter/gdm-sessions.h
new file mode 100644
index 0000000..217c67c
--- /dev/null
+++ b/gui/simple-greeter/gdm-sessions.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2008 Red Hat, Inc.
+ * Copyright 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Written by: Ray Strode
+ *             William Jon McCann
+ */
+
+#ifndef __GDM_SESSIONS_H
+#define __GDM_SESSIONS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+char **                gdm_get_all_sessions (void);
+gboolean               gdm_get_details_for_session (const char  *id,
+                                                    char       **name,
+                                                    char       **comment);
+
+G_END_DECLS
+
+#endif /* __GDM_SESSION_H */
diff --git a/gui/simple-greeter/gdm-timer.c b/gui/simple-greeter/gdm-timer.c
new file mode 100644
index 0000000..9acd3f7
--- /dev/null
+++ b/gui/simple-greeter/gdm-timer.c
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Written by: Ray Strode <rstrode redhat com>
+ */
+
+#include "config.h"
+#include "gdm-timer.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>
+
+#define GDM_TIMER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_TIMER, GdmTimerPrivate))
+
+#ifndef GDM_TIMER_TICKS_PER_SECOND
+#define GDM_TIMER_TICKS_PER_SECOND 60
+#endif
+
+struct GdmTimerPrivate
+{
+        double start_time;
+        double duration;
+
+        guint  tick_timeout_id;
+
+        guint  is_started : 1;
+};
+
+enum {
+        PROP_0,
+        PROP_START_TIME,
+        PROP_DURATION,
+        PROP_IS_STARTED
+};
+
+enum {
+        TICK,
+        STOP,
+        NUMBER_OF_SIGNALS
+};
+
+static guint signals[NUMBER_OF_SIGNALS];
+
+static void  gdm_timer_class_init  (GdmTimerClass *klass);
+static void  gdm_timer_init        (GdmTimer      *timer);
+static void  gdm_timer_finalize    (GObject       *object);
+
+static void  gdm_timer_queue_next_tick (GdmTimer *timer,
+                                        double    when);
+
+G_DEFINE_TYPE (GdmTimer, gdm_timer, G_TYPE_OBJECT)
+
+static void
+gdm_timer_finalize (GObject *object)
+{
+        GdmTimer *timer;
+
+        timer = GDM_TIMER (object);
+
+        gdm_timer_stop (timer);
+
+        G_OBJECT_CLASS (gdm_timer_parent_class)->finalize (object);
+}
+
+static void
+gdm_timer_get_property (GObject        *object,
+                        guint           prop_id,
+                        GValue         *value,
+                        GParamSpec     *pspec)
+{
+        GdmTimer *timer;
+
+        timer = GDM_TIMER (object);
+
+        switch (prop_id) {
+        case PROP_START_TIME:
+                g_value_set_double (value, timer->priv->start_time);
+                break;
+        case PROP_DURATION:
+                g_value_set_double (value, timer->priv->duration);
+                break;
+        case PROP_IS_STARTED:
+                g_value_set_boolean (value, timer->priv->is_started);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gdm_timer_class_init (GdmTimerClass *klass)
+{
+        GObjectClass *object_class;
+
+        object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gdm_timer_finalize;
+
+        object_class->get_property = gdm_timer_get_property;
+
+        signals[TICK] = g_signal_new ("tick",
+                                      G_TYPE_FROM_CLASS (object_class),
+                                      G_SIGNAL_RUN_LAST,
+                                      G_STRUCT_OFFSET (GdmTimerClass, tick),
+                                      NULL,
+                                      NULL,
+                                      g_cclosure_marshal_generic,
+                                      G_TYPE_NONE,
+                                      1, G_TYPE_DOUBLE);
+        signals[STOP] = g_signal_new ("stop",
+                                      G_TYPE_FROM_CLASS (object_class),
+                                      G_SIGNAL_RUN_LAST,
+                                      G_STRUCT_OFFSET (GdmTimerClass, stop),
+                                      NULL,
+                                      NULL,
+                                      g_cclosure_marshal_VOID__VOID,
+                                      G_TYPE_NONE, 0);
+
+        g_object_class_install_property (object_class,
+                                         PROP_DURATION,
+                                         g_param_spec_double ("duration",
+                                                              _("Duration"),
+                                                              _("Number of seconds until timer stops"),
+                                                              0.0, G_MAXDOUBLE, 0.0,
+                                                              G_PARAM_READABLE));
+
+        g_object_class_install_property (object_class,
+                                         PROP_START_TIME,
+                                         g_param_spec_double ("start-time",
+                                                              _("Start time"),
+                                                              _("Time the timer was started"),
+                                                              0.0, G_MAXDOUBLE, 0.0,
+                                                              G_PARAM_READABLE));
+
+        g_object_class_install_property (object_class,
+                                         PROP_IS_STARTED,
+                                         g_param_spec_boolean ("is-started",
+                                                               _("Is it Running?"),
+                                                               _("Whether the timer "
+                                                                 "is currently ticking"),
+                                                               FALSE, G_PARAM_READABLE));
+
+
+
+        g_type_class_add_private (klass, sizeof (GdmTimerPrivate));
+}
+
+static void
+gdm_timer_init (GdmTimer *timer)
+{
+        timer->priv = GDM_TIMER_GET_PRIVATE (timer);
+}
+
+GdmTimer *
+gdm_timer_new (void)
+{
+        GObject *object;
+
+        object = g_object_new (GDM_TYPE_TIMER, NULL);
+
+        return GDM_TIMER (object);
+}
+
+static double
+get_current_time (void)
+{
+  const double microseconds_per_second = 1000000.0;
+  double       timestamp;
+  GTimeVal     now;
+
+  g_get_current_time (&now);
+
+  timestamp = ((microseconds_per_second * now.tv_sec) + now.tv_usec) /
+               microseconds_per_second;
+
+  return timestamp;
+}
+
+static double
+do_tick (GdmTimer *timer,
+         double    progress,
+         double    current_time)
+{
+        static const double frequency = 1.0 / GDM_TIMER_TICKS_PER_SECOND;
+        double next_tick;
+        double time_before_tick;
+        double tick_duration;
+
+        time_before_tick = current_time;
+        g_signal_emit (G_OBJECT (timer), signals[TICK], 0, progress);
+
+        current_time = get_current_time ();
+        tick_duration = current_time - time_before_tick;
+
+        next_tick = MAX (frequency - tick_duration, 0.0);
+
+        return next_tick;
+}
+
+static gboolean
+on_tick_timeout (GdmTimer *timer)
+{
+        double progress;
+        double current_time;
+        double elapsed_time;
+        double next_tick;
+
+        current_time = get_current_time ();
+        elapsed_time = current_time - timer->priv->start_time;
+        progress = elapsed_time / timer->priv->duration;
+
+        timer->priv->tick_timeout_id = 0;
+
+        g_object_ref (timer);
+        if (progress > 0.999) {
+                do_tick (timer, 1.0, current_time);
+                if (timer->priv->is_started) {
+                        gdm_timer_stop (timer);
+                }
+        } else {
+                next_tick = do_tick (timer, progress, current_time);
+                if (timer->priv->is_started) {
+                        gdm_timer_queue_next_tick (timer, next_tick);
+                }
+        }
+        g_object_unref (timer);
+
+        return FALSE;
+}
+
+static void
+gdm_timer_queue_next_tick (GdmTimer *timer,
+                           double    when)
+{
+        if (timer->priv->tick_timeout_id != 0) {
+                return;
+        }
+
+        if (!timer->priv->is_started) {
+                return;
+        }
+
+        timer->priv->tick_timeout_id = g_timeout_add ((guint) (when * 1000),
+                                                      (GSourceFunc) on_tick_timeout,
+                                                      timer);
+}
+
+static void
+gdm_timer_set_is_started (GdmTimer *timer,
+                          gboolean  is_started)
+{
+        timer->priv->is_started = is_started;
+        g_object_notify (G_OBJECT (timer), "is-started");
+}
+
+void
+gdm_timer_start (GdmTimer *timer,
+                 double    number_of_seconds)
+{
+        double next_tick;
+
+        g_return_if_fail (GDM_IS_TIMER (timer));
+        g_return_if_fail (number_of_seconds > G_MINDOUBLE);
+        g_return_if_fail (!timer->priv->is_started);
+
+        timer->priv->start_time = get_current_time ();
+        timer->priv->duration = number_of_seconds;
+
+        g_assert (timer->priv->tick_timeout_id == 0);
+        gdm_timer_set_is_started (timer, TRUE);
+
+        g_object_ref (timer);
+        next_tick = do_tick (timer, 0.0, timer->priv->start_time);
+        gdm_timer_queue_next_tick (timer, next_tick);
+        g_object_unref (timer);
+}
+
+void
+gdm_timer_stop (GdmTimer *timer)
+{
+        g_return_if_fail (GDM_IS_TIMER (timer));
+
+        if (!timer->priv->is_started) {
+                return;
+        }
+
+        if (timer->priv->tick_timeout_id != 0) {
+                g_source_remove (timer->priv->tick_timeout_id);
+                timer->priv->tick_timeout_id = 0;
+        }
+
+        gdm_timer_set_is_started (timer, FALSE);
+        g_signal_emit (G_OBJECT (timer), signals[STOP], 0);
+}
+
+gboolean
+gdm_timer_is_started (GdmTimer *timer)
+{
+        g_return_val_if_fail (GDM_IS_TIMER (timer), FALSE);
+
+        return timer->priv->is_started;
+}
diff --git a/gui/simple-greeter/gdm-timer.h b/gui/simple-greeter/gdm-timer.h
new file mode 100644
index 0000000..099eee5
--- /dev/null
+++ b/gui/simple-greeter/gdm-timer.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *  Written by: Ray Strode <rstrode redhat com>
+ */
+
+#ifndef GDM_TIMER_H
+#define GDM_TIMER_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_TIMER         (gdm_timer_get_type ())
+#define GDM_TIMER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_TIMER, GdmTimer))
+#define GDM_TIMER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_TIMER, GdmTimerClass))
+#define GDM_IS_TIMER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_TIMER))
+#define GDM_IS_TIMER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_TIMER))
+#define GDM_TIMER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_TIMER, GdmTimerClass))
+
+typedef struct GdmTimerPrivate GdmTimerPrivate;
+
+typedef struct
+{
+        GObject          parent;
+        GdmTimerPrivate *priv;
+} GdmTimer;
+
+typedef struct
+{
+        GObjectClass   parent_class;
+
+        void (* tick)  (GdmTimer *timer, double progress);
+        void (* stop)  (GdmTimer *timer);
+} GdmTimerClass;
+
+GType     gdm_timer_get_type       (void);
+GdmTimer *gdm_timer_new            (void);
+#if 0
+GObject *gdm_timer_new_for_source (GdmTimerSource *source);
+#endif
+void      gdm_timer_start          (GdmTimer *timer,
+                                   double    number_of_seconds);
+void      gdm_timer_stop           (GdmTimer *timer);
+gboolean  gdm_timer_is_started     (GdmTimer *timer);
+
+#endif /* GDM_TIMER_H */
diff --git a/gui/simple-greeter/gdm-user-chooser-dialog.c b/gui/simple-greeter/gdm-user-chooser-dialog.c
new file mode 100644
index 0000000..639437f
--- /dev/null
+++ b/gui/simple-greeter/gdm-user-chooser-dialog.c
@@ -0,0 +1,199 @@
+/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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_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_get_content_area (GTK_DIALOG (dialog))), 
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);
+}
diff --git a/gui/simple-greeter/gdm-user-chooser-dialog.h b/gui/simple-greeter/gdm-user-chooser-dialog.h
new file mode 100644
index 0000000..dd203c5
--- /dev/null
+++ b/gui/simple-greeter/gdm-user-chooser-dialog.h
@@ -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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef __GDM_USER_CHOOSER_DIALOG_H
+#define __GDM_USER_CHOOSER_DIALOG_H
+
+#include <glib-object.h>
+#include <gtk/gtk.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 */
diff --git a/gui/simple-greeter/gdm-user-chooser-widget.c b/gui/simple-greeter/gdm-user-chooser-widget.c
new file mode 100644
index 0000000..ccc07a3
--- /dev/null
+++ b/gui/simple-greeter/gdm-user-chooser-widget.c
@@ -0,0 +1,1366 @@
+/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <dirent.h>
+#include <stdarg.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include <act/act-user-manager.h>
+#include <act/act-user.h>
+
+#include "gdm-user-chooser-widget.h"
+#include "gdm-settings-keys.h"
+#include "gdm-settings-client.h"
+
+#define LOGIN_SCREEN_SCHEMA   "org.gnome.login-screen"
+
+#define KEY_DISABLE_USER_LIST "disable-user-list"
+
+enum {
+        USER_NO_DISPLAY              = 1 << 0,
+        USER_ACCOUNT_DISABLED        = 1 << 1,
+};
+
+#define DEFAULT_USER_ICON "avatar-default"
+#define OLD_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
+#define NUM_USERS_TO_ADD_PER_ITERATION 50
+
+struct GdmUserChooserWidgetPrivate
+{
+        ActUserManager *manager;
+        GtkIconTheme   *icon_theme;
+
+        GSList         *users_to_add;
+
+        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           has_user_other : 1;
+
+        guint           update_other_visibility_idle_id;
+        guint           load_idle_id;
+        guint           add_users_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 void     add_user_other    (GdmUserChooserWidget *widget);
+static void     remove_user_other (GdmUserChooserWidget *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,
+                                             gtk_widget_get_style (widget)->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 gboolean
+update_other_user_visibility (GdmUserChooserWidget *widget)
+{
+        g_debug ("GdmUserChooserWidget: updating other user visibility");
+
+        if (!widget->priv->show_user_other) {
+                if (widget->priv->has_user_other) {
+                        remove_user_other (widget);
+                }
+
+                goto out;
+        }
+
+        /* Always show the Other user if requested */
+        if (!widget->priv->has_user_other) {
+                add_user_other (widget);
+        }
+
+ out:
+        widget->priv->update_other_visibility_idle_id = 0;
+        return FALSE;
+}
+
+static void
+queue_update_other_user_visibility (GdmUserChooserWidget *widget)
+{
+        if (widget->priv->update_other_visibility_idle_id == 0) {
+                widget->priv->update_other_visibility_idle_id =
+                        g_idle_add ((GSourceFunc) update_other_user_visibility, widget);
+        }
+}
+
+static void
+rounded_rectangle (cairo_t *cr,
+                   gdouble  aspect,
+                   gdouble  x,
+                   gdouble  y,
+                   gdouble  corner_radius,
+                   gdouble  width,
+                   gdouble  height)
+{
+        gdouble radius;
+        gdouble degrees;
+
+        radius = corner_radius / aspect;
+        degrees = G_PI / 180.0;
+
+        cairo_new_sub_path (cr);
+        cairo_arc (cr,
+                   x + width - radius,
+                   y + radius,
+                   radius,
+                   -90 * degrees,
+                   0 * degrees);
+        cairo_arc (cr,
+                   x + width - radius,
+                   y + height - radius,
+                   radius,
+                   0 * degrees,
+                   90 * degrees);
+        cairo_arc (cr,
+                   x + radius,
+                   y + height - radius,
+                   radius,
+                   90 * degrees,
+                   180 * degrees);
+        cairo_arc (cr,
+                   x + radius,
+                   y + radius,
+                   radius,
+                   180 * degrees,
+                   270 * degrees);
+        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 / 10;
+
+        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);
+
+        rounded_rectangle (cr,
+                           1.0,
+                           frame_width + 0.5,
+                           frame_width + 0.5,
+                           radius,
+                           w - frame_width * 2 - 1,
+                           h - frame_width * 2 - 1);
+        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;
+}
+
+static GdkPixbuf *
+render_user_icon (GdmUserChooserWidget *widget,
+                  ActUser              *user)
+{
+        int size;
+        const char *file;
+        GdkPixbuf *pixbuf;
+        GdkPixbuf *framed;
+
+        pixbuf = NULL;
+
+        size = get_icon_height_for_widget (GTK_WIDGET (widget));
+        file = act_user_get_icon_file (user);
+
+        if (file) {
+                pixbuf = gdk_pixbuf_new_from_file_at_size (file, size, size, NULL);
+        }
+
+        if (pixbuf == NULL) {
+                GError *error;
+
+                error = NULL;
+                pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+                                                   "avatar-default",
+                                                   size,
+                                                   GTK_ICON_LOOKUP_FORCE_SIZE,
+                                                   &error);
+                if (error != NULL) {
+                        g_warning ("%s", error->message);
+                        g_error_free (error);
+                }
+        }
+
+        if (pixbuf != NULL) {
+                framed = frame_pixbuf (pixbuf);
+                g_object_unref (pixbuf);
+
+                pixbuf = framed;
+        }
+
+        return pixbuf;
+}
+
+static void
+update_item_for_user (GdmUserChooserWidget *widget,
+                      ActUser              *user)
+{
+        GdkPixbuf    *pixbuf;
+        char         *tooltip;
+        gboolean      is_logged_in;
+        char         *escaped_username;
+        const char   *real_name;
+        char         *escaped_real_name;
+
+        if (!act_user_is_loaded (user)) {
+                return;
+        }
+
+        pixbuf = render_user_icon (widget, user);
+
+        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"),
+                                   act_user_get_user_name (user));
+
+        is_logged_in = act_user_is_logged_in (user);
+
+        g_debug ("GdmUserChooserWidget: User added name:%s logged-in:%d pixbuf:%p",
+                 act_user_get_user_name (user),
+                 is_logged_in,
+                 pixbuf);
+
+        escaped_username = g_markup_escape_text (act_user_get_user_name (user), -1);
+
+        real_name = act_user_get_real_name (user);
+        if (real_name == NULL || real_name[0] == '\0') {
+                real_name = act_user_get_user_name (user);
+        }
+        escaped_real_name = g_markup_escape_text (real_name, -1);
+
+        /* Ignore updates we aren't interested in */
+        if (!gdm_chooser_widget_lookup_item (GDM_CHOOSER_WIDGET (widget),
+                                             escaped_username,
+                                             NULL,
+                                             NULL,
+                                             NULL,
+                                             NULL,
+                                             NULL,
+                                             NULL)) {
+                return;
+        }
+
+        gdm_chooser_widget_update_item (GDM_CHOOSER_WIDGET (widget),
+                                        escaped_username,
+                                        pixbuf,
+                                        escaped_real_name,
+                                        tooltip,
+                                        act_user_get_login_frequency (user),
+                                        is_logged_in,
+                                        FALSE);
+        g_free (escaped_real_name);
+        g_free (escaped_username);
+        g_free (tooltip);
+
+        if (pixbuf != NULL) {
+                g_object_unref (pixbuf);
+        }
+}
+
+static void
+on_item_load (GdmChooserWidget     *widget,
+              const char           *id,
+              GdmUserChooserWidget *user_chooser)
+{
+        ActUser *user;
+
+        g_debug ("GdmUserChooserWidget: Loading item for id=%s", id);
+
+        if (user_chooser->priv->manager == NULL) {
+                return;
+        }
+
+        if (strcmp (id, GDM_USER_CHOOSER_USER_OTHER) == 0) {
+                return;
+        }
+
+        if (strcmp (id, GDM_USER_CHOOSER_USER_GUEST) == 0) {
+                return;
+        }
+
+        user = act_user_manager_get_user (user_chooser->priv->manager, id);
+        if (user != NULL) {
+                update_item_for_user (user_chooser, user);
+        }
+}
+
+static void
+add_user_other (GdmUserChooserWidget *widget)
+{
+        widget->priv->has_user_other = TRUE;
+        gdm_chooser_widget_add_item (GDM_CHOOSER_WIDGET (widget),
+                                     GDM_USER_CHOOSER_USER_OTHER,
+                                     NULL,
+                                     /* translators: This option prompts
+                                      * the user to type in a username
+                                      * manually instead of choosing from
+                                      * a list.
+                                      */
+                                     C_("user", "Other…"),
+                                     _("Choose a different account"),
+                                     0,
+                                     FALSE,
+                                     TRUE,
+                                     (GdmChooserWidgetItemLoadFunc) on_item_load,
+                                     widget);
+}
+
+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"),
+                                     _("Log in as a temporary guest"),
+                                     0,
+                                     FALSE,
+                                     TRUE,
+                                     (GdmChooserWidgetItemLoadFunc) on_item_load,
+                                     widget);
+        queue_update_other_user_visibility (widget);
+}
+
+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 log into the system after selecting options"),
+                                     0,
+                                     FALSE,
+                                     TRUE,
+                                     (GdmChooserWidgetItemLoadFunc) on_item_load,
+                                     widget);
+        queue_update_other_user_visibility (widget);
+}
+
+static void
+remove_user_other (GdmUserChooserWidget *widget)
+{
+        widget->priv->has_user_other = FALSE;
+        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);
+        queue_update_other_user_visibility (widget);
+}
+
+static void
+remove_user_auto (GdmUserChooserWidget *widget)
+{
+        gdm_chooser_widget_remove_item (GDM_CHOOSER_WIDGET (widget),
+                                        GDM_USER_CHOOSER_USER_AUTO);
+        queue_update_other_user_visibility (widget);
+}
+
+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;
+                queue_update_other_user_visibility (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)
+{
+        char *active_item_id;
+        gboolean isnt_user;
+
+        g_return_val_if_fail (GDM_IS_USER_CHOOSER_WIDGET (widget), NULL);
+
+        active_item_id = gdm_chooser_widget_get_active_item (GDM_CHOOSER_WIDGET (widget));
+        if (active_item_id == NULL) {
+                g_debug ("GdmUserChooserWidget: no active item in list");
+                return NULL;
+        }
+
+        gdm_chooser_widget_lookup_item (GDM_CHOOSER_WIDGET (widget), active_item_id,
+                                        NULL, NULL, NULL, NULL, NULL,
+                                        &isnt_user);
+
+        if (isnt_user) {
+                g_debug ("GdmUserChooserWidget: active item '%s' isn't a user", active_item_id);
+                g_free (active_item_id);
+                return NULL;
+        }
+
+        g_debug ("GdmUserChooserWidget: active item '%s' is a user", active_item_id);
+
+        return active_item_id;
+}
+
+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)
+{
+        GSettings *settings;
+        gboolean   result;
+
+        settings = g_settings_new (LOGIN_SCREEN_SCHEMA);
+        result = g_settings_get_boolean (settings, KEY_DISABLE_USER_LIST);
+        g_object_unref (settings);
+
+        return result;
+}
+
+static void
+add_user (GdmUserChooserWidget *widget,
+          ActUser              *user)
+{
+        GdkPixbuf    *pixbuf;
+        char         *tooltip;
+        gboolean      is_logged_in;
+        char         *escaped_username;
+        const char   *real_name;
+        char         *escaped_real_name;
+
+        if (!widget->priv->show_normal_users) {
+                return;
+        }
+
+        if (strcmp (act_user_get_user_name (user), GDM_USERNAME) == 0) {
+                return;
+        }
+
+        if (act_user_get_uid (user) == 0) {
+                return;
+        }
+
+        if (act_user_get_locked (user)) {
+                g_debug ("GdmUserChooserWidget: Skipping locked user: %s", act_user_get_user_name (user));
+                return;
+        }
+
+        g_debug ("GdmUserChooserWidget: User added: %s", act_user_get_user_name (user));
+        if (widget->priv->stock_person_pixbuf != NULL) {
+                pixbuf = g_object_ref (widget->priv->stock_person_pixbuf);
+        } else {
+                pixbuf = NULL;
+        }
+
+        tooltip = g_strdup_printf (_("Log in as %s"),
+                                   act_user_get_user_name (user));
+
+        is_logged_in = act_user_is_logged_in (user);
+
+        escaped_username = g_markup_escape_text (act_user_get_user_name (user), -1);
+        real_name = act_user_get_real_name (user);
+        if (real_name == NULL || real_name[0] == '\0') {
+                real_name = act_user_get_user_name (user);
+        }
+        escaped_real_name = g_markup_escape_text (real_name, -1);
+
+        gdm_chooser_widget_add_item (GDM_CHOOSER_WIDGET (widget),
+                                     escaped_username,
+                                     pixbuf,
+                                     escaped_real_name,
+                                     tooltip,
+                                     act_user_get_login_frequency (user),
+                                     is_logged_in,
+                                     FALSE,
+                                     (GdmChooserWidgetItemLoadFunc) on_item_load,
+                                     widget);
+        g_free (escaped_real_name);
+        g_free (escaped_username);
+        g_free (tooltip);
+
+        if (pixbuf != NULL) {
+                g_object_unref (pixbuf);
+        }
+
+        queue_update_other_user_visibility (widget);
+}
+
+static void
+on_user_added (ActUserManager       *manager,
+               ActUser              *user,
+               GdmUserChooserWidget *widget)
+{
+        /* wait for all users to be loaded */
+        if (! widget->priv->loaded) {
+                return;
+        }
+        add_user (widget, user);
+}
+
+static void
+on_user_removed (ActUserManager       *manager,
+                 ActUser              *user,
+                 GdmUserChooserWidget *widget)
+{
+        const char *user_name;
+
+        g_debug ("GdmUserChooserWidget: User removed: %s", act_user_get_user_name (user));
+        /* wait for all users to be loaded */
+        if (! widget->priv->loaded) {
+                return;
+        }
+
+        user_name = act_user_get_user_name (user);
+
+        /* Ignore removals we aren't interested in */
+        if (!gdm_chooser_widget_lookup_item (GDM_CHOOSER_WIDGET (widget),
+                                             user_name,
+                                             NULL,
+                                             NULL,
+                                             NULL,
+                                             NULL,
+                                             NULL,
+                                             NULL)) {
+                return;
+        }
+
+        gdm_chooser_widget_remove_item (GDM_CHOOSER_WIDGET (widget),
+                                        user_name);
+
+        queue_update_other_user_visibility (widget);
+}
+
+static void
+on_user_is_logged_in_changed (ActUserManager       *manager,
+                              ActUser              *user,
+                              GdmUserChooserWidget *widget)
+{
+        const char *user_name;
+        gboolean    is_logged_in;
+
+        g_debug ("GdmUserChooserWidget: User logged in changed: %s", act_user_get_user_name (user));
+
+        user_name = act_user_get_user_name (user);
+        is_logged_in = act_user_is_logged_in (user);
+
+        gdm_chooser_widget_set_item_in_use (GDM_CHOOSER_WIDGET (widget),
+                                            user_name,
+                                            is_logged_in);
+}
+
+static void
+on_user_changed (ActUserManager       *manager,
+                 ActUser              *user,
+                 GdmUserChooserWidget *widget)
+{
+        /* wait for all users to be loaded */
+        if (! widget->priv->loaded) {
+                return;
+        }
+        if (! widget->priv->show_normal_users) {
+                return;
+        }
+
+        update_item_for_user (widget, user);
+}
+
+static gboolean
+add_users (GdmUserChooserWidget *widget)
+{
+        guint cnt;
+
+        cnt = 0;
+        while (widget->priv->users_to_add != NULL && cnt < NUM_USERS_TO_ADD_PER_ITERATION) {
+                add_user (widget, widget->priv->users_to_add->data);
+                g_object_unref (widget->priv->users_to_add->data);
+                widget->priv->users_to_add = g_slist_delete_link (widget->priv->users_to_add, 
widget->priv->users_to_add);
+                cnt++;
+        }
+        g_debug ("GdmUserChooserWidget: added %u items", cnt);
+
+        if (! widget->priv->loaded) {
+                widget->priv->loaded = TRUE;
+
+                gdm_chooser_widget_loaded (GDM_CHOOSER_WIDGET (widget));
+        }
+
+        if (widget->priv->users_to_add == NULL) {
+            widget->priv->add_users_idle_id = 0;
+            return FALSE;
+        }
+
+        return TRUE;
+}
+
+static void
+queue_add_users (GdmUserChooserWidget *widget)
+{
+        if (widget->priv->add_users_idle_id == 0) {
+                widget->priv->add_users_idle_id = g_idle_add ((GSourceFunc) add_users, widget);
+        }
+}
+
+static void
+on_is_loaded_changed (ActUserManager       *manager,
+                      GParamSpec           *pspec,
+                      GdmUserChooserWidget *widget)
+{
+        GSList *users;
+
+        /* FIXME: handle is-loaded=FALSE */
+
+        g_debug ("GdmUserChooserWidget: Users loaded");
+
+        users = act_user_manager_list_users (manager);
+        g_slist_foreach (users, (GFunc) g_object_ref, NULL);
+        widget->priv->users_to_add = g_slist_concat (widget->priv->users_to_add, g_slist_copy (users));
+
+        queue_add_users (widget);
+}
+
+static gboolean
+load_users (GdmUserChooserWidget *widget)
+{
+
+        if (widget->priv->show_normal_users) {
+                widget->priv->manager = act_user_manager_get_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,
+                                  "notify::is-loaded",
+                                  G_CALLBACK (on_is_loaded_changed),
+                                  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-changed",
+                                  G_CALLBACK (on_user_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->add_users_idle_id > 0) {
+                g_source_remove (widget->priv->add_users_idle_id);
+                widget->priv->add_users_idle_id = 0;
+        }
+
+        if (widget->priv->update_other_visibility_idle_id > 0) {
+                g_source_remove (widget->priv->update_other_visibility_idle_id);
+                widget->priv->update_other_visibility_idle_id = 0;
+        }
+
+        if (widget->priv->users_to_add != NULL) {
+                g_slist_foreach (widget->priv->users_to_add, (GFunc) g_object_ref, NULL);
+                g_slist_free (widget->priv->users_to_add);
+                widget->priv->users_to_add = NULL;
+        }
+
+        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;
+        }
+
+        if (widget->priv->manager != NULL) {
+                g_signal_handlers_disconnect_by_func (widget->priv->manager,
+                                                      G_CALLBACK (on_user_added),
+                                                      widget);
+                g_signal_handlers_disconnect_by_func (widget->priv->manager,
+                                                      G_CALLBACK (on_user_removed),
+                                                      widget);
+                g_signal_handlers_disconnect_by_func (widget->priv->manager,
+                                                      G_CALLBACK (on_is_loaded_changed),
+                                                      widget);
+                g_signal_handlers_disconnect_by_func (widget->priv->manager,
+                                                      G_CALLBACK (on_user_is_logged_in_changed),
+                                                      widget);
+                g_signal_handlers_disconnect_by_func (widget->priv->manager,
+                                                      G_CALLBACK (on_user_changed),
+                                                      widget);
+
+                g_object_unref (widget->priv->manager);
+                widget->priv->manager = 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_pixbuf_from_icon_names (GdmUserChooserWidget *widget,
+                            const char           *first_name,
+                            ...)
+{
+        GdkPixbuf   *pixbuf;
+        GtkIconInfo *icon_info;
+        GPtrArray   *array;
+        int          size;
+        const char  *icon_name;
+        va_list      argument_list;
+        gint        *sizes;
+        gint         candidate_size;
+        int          i;
+
+        array = g_ptr_array_new ();
+
+        g_ptr_array_add (array, (gpointer) first_name);
+
+        va_start (argument_list, first_name);
+        icon_name = (const char *) va_arg (argument_list, const char *);
+        while (icon_name != NULL) {
+                g_ptr_array_add (array, (gpointer) icon_name);
+                icon_name = (const char *) va_arg (argument_list, const char *);
+        }
+        va_end (argument_list);
+        g_ptr_array_add (array, NULL);
+
+        size = get_icon_height_for_widget (GTK_WIDGET (widget));
+
+        sizes = gtk_icon_theme_get_icon_sizes (widget->priv->icon_theme, first_name);
+
+        candidate_size = 0;
+        for (i = 0; sizes[i] != 0; i++) {
+
+                /* scalable */
+                if (sizes[i] == -1) {
+                        candidate_size = sizes[i];
+                        break;
+                }
+
+                if (ABS (size - sizes[i]) < ABS (size - candidate_size)) {
+                        candidate_size = sizes[i];
+                }
+        }
+
+        if (candidate_size == 0) {
+                candidate_size = size;
+        }
+
+        icon_info = gtk_icon_theme_choose_icon (widget->priv->icon_theme,
+                                                (const char **) array->pdata,
+                                                candidate_size,
+                                                GTK_ICON_LOOKUP_GENERIC_FALLBACK);
+        g_ptr_array_free (array, FALSE);
+
+        if (icon_info != NULL) {
+                GError *error;
+
+                error = NULL;
+                pixbuf = gtk_icon_info_load_icon (icon_info, &error);
+                gtk_icon_info_free (icon_info);
+
+                if (error != NULL) {
+                        g_warning ("Could not load icon '%s': %s",
+                                   first_name, error->message);
+                        g_error_free (error);
+                }
+        } else {
+                GdkPixbuf *scaled_pixbuf;
+
+                guchar pixel = 0x00000000;
+
+                g_warning ("Could not find icon '%s' or fallbacks", first_name);
+                pixbuf = gdk_pixbuf_new_from_data (&pixel, GDK_COLORSPACE_RGB,
+                                                   TRUE, 8, 1, 1, 1, NULL, NULL);
+                scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf, size, size, GDK_INTERP_NEAREST);
+                g_object_unref (pixbuf);
+                pixbuf = scaled_pixbuf;
+        }
+
+        return pixbuf;
+}
+
+static GdkPixbuf *
+get_stock_person_pixbuf (GdmUserChooserWidget *widget)
+{
+        GdkPixbuf   *pixbuf;
+
+        pixbuf = get_pixbuf_from_icon_names (widget,
+                                             DEFAULT_USER_ICON,
+                                             OLD_DEFAULT_USER_ICON,
+                                             NULL);
+
+        return pixbuf;
+}
+
+static GdkPixbuf *
+get_logged_in_pixbuf (GdmUserChooserWidget *widget)
+{
+        GdkPixbuf *pixbuf;
+
+        pixbuf = get_pixbuf_from_icon_names (widget,
+                                             DEFAULT_USER_ICON,
+                                             "emblem-default",
+                                             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) {
+                if (*image != NULL) {
+                        g_object_unref (*image);
+                }
+                *image = data->new_icon;
+
+                if (*image != NULL) {
+                        g_object_ref (*image);
+                }
+                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
+on_list_visible_changed (GdmChooserWidget *widget,
+                         GParamSpec       *pspec,
+                         gpointer          data)
+{
+        gboolean is_visible;
+
+        g_object_get (G_OBJECT (widget), "list-visible", &is_visible, NULL);
+        if (is_visible) {
+                gtk_widget_grab_focus (GTK_WIDGET (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"));
+
+        g_signal_connect (widget,
+                          "notify::list-visible",
+                          G_CALLBACK (on_list_visible_changed),
+                          NULL);
+
+        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);
+}
diff --git a/gui/simple-greeter/gdm-user-chooser-widget.h b/gui/simple-greeter/gdm-user-chooser-widget.h
new file mode 100644
index 0000000..d670ed7
--- /dev/null
+++ b/gui/simple-greeter/gdm-user-chooser-widget.h
@@ -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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 */
diff --git a/gui/simple-greeter/gdm-user-private.h b/gui/simple-greeter/gdm-user-private.h
new file mode 100644
index 0000000..9bf3ca1
--- /dev/null
+++ b/gui/simple-greeter/gdm-user-private.h
@@ -0,0 +1,49 @@
+/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * Private interfaces to the GdmUser object
+ */
+
+#ifndef __GDM_USER_PRIVATE_H_
+#define __GDM_USER_PRIVATE_H_
+
+#include <pwd.h>
+
+#include "gdm-user.h"
+
+G_BEGIN_DECLS
+
+void _gdm_user_update_from_object_path (GdmUser    *user,
+                                        const char *object_path);
+
+void _gdm_user_update_from_pwent (GdmUser             *user,
+                                  const struct passwd *pwent);
+
+void _gdm_user_update_login_frequency (GdmUser *user,
+                                       guint64  login_frequency);
+
+void _gdm_user_add_session      (GdmUser             *user,
+                                 const char          *session_id);
+void _gdm_user_remove_session   (GdmUser             *user,
+                                 const char          *session_id);
+
+G_END_DECLS
+
+#endif /* !__GDM_USER_PRIVATE__ */
diff --git a/gui/simple-greeter/greeter-main.c b/gui/simple-greeter/greeter-main.c
new file mode 100644
index 0000000..b17da46
--- /dev/null
+++ b/gui/simple-greeter/greeter-main.c
@@ -0,0 +1,284 @@
+/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <locale.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include "gdm-log.h"
+#include "gdm-common.h"
+#include "gdm-settings-client.h"
+#include "gdm-settings-keys.h"
+#include "gdm-profile.h"
+
+#include "gdm-greeter-session.h"
+
+#include "gsm-client-glue.h"
+#include "gsm-manager-glue.h"
+
+#define SM_DBUS_NAME      "org.gnome.SessionManager"
+#define SM_DBUS_PATH      "/org/gnome/SessionManager"
+#define SM_DBUS_INTERFACE "org.gnome.SessionManager"
+
+#define SM_CLIENT_DBUS_INTERFACE "org.gnome.SessionManager.ClientPrivate"
+
+static GDBusConnection  *bus_connection = NULL;
+static GsmManager       *sm_proxy = NULL;
+static char             *client_id = NULL;
+static GsmClientPrivate *client_proxy = NULL;
+
+static gboolean
+is_debug_set (void)
+{
+        gboolean debug = FALSE;
+
+        /* enable debugging for unstable builds */
+        if (gdm_is_version_unstable ()) {
+                return TRUE;
+        }
+
+        gdm_settings_client_get_boolean (GDM_KEY_DEBUG, &debug);
+        return debug;
+}
+
+static gboolean
+on_sigusr1_cb (gpointer user_data)
+{
+        g_debug ("Got USR1 signal");
+
+        gdm_log_toggle_debug ();
+
+        return TRUE;
+}
+
+static gboolean
+session_manager_connect (void)
+{
+        GError *error;
+
+        error = NULL;
+
+        if (bus_connection == NULL) {
+                bus_connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+                if (bus_connection == NULL) {
+                        g_message ("Failed to connect to the session bus: %s",
+                                   error->message);
+                        g_error_free (error);
+                        exit (1);
+                }
+
+                g_signal_connect (G_OBJECT (bus_connection),
+                                  "closed",
+                                  G_CALLBACK (gtk_main_quit),
+                                  NULL);
+        }
+
+        sm_proxy = gsm_manager_proxy_new_sync (bus_connection,
+                                               G_DBUS_PROXY_FLAGS_NONE,
+                                               SM_DBUS_NAME,
+                                               SM_DBUS_PATH,
+                                               NULL,
+                                               &error);
+
+        if (sm_proxy == NULL) {
+                g_message ("Failed to connect to the session manager: %s",
+                           error->message);
+                g_error_free (error);
+        }
+
+        return (sm_proxy != NULL);
+}
+
+static void
+stop_cb (GsmClientPrivate *client_private,
+         gpointer          data)
+{
+        gtk_main_quit ();
+}
+
+static gboolean
+end_session_response (gboolean is_okay, const gchar *reason)
+{
+        gboolean ret;
+        GError *error = NULL;
+
+        if (reason == NULL) {
+                reason = "";
+        }
+
+        ret = gsm_client_private_call_end_session_response_sync (client_proxy, is_okay, reason, NULL, 
&error);
+
+        if (!ret) {
+                g_warning ("Failed to send session response %s", error->message);
+                g_error_free (error);
+        }
+
+        return ret;
+}
+
+static void
+query_end_session_cb (GsmClientPrivate *client_private,
+                      guint             flags,
+                      gpointer          data)
+{
+        end_session_response (TRUE, NULL);
+}
+
+static void
+end_session_cb (guint flags, gpointer data)
+{
+        end_session_response (TRUE, NULL);
+        gtk_main_quit ();
+}
+
+static gboolean
+register_client (void)
+{
+        GError     *error;
+        gboolean    res;
+        const char *startup_id;
+        const char *app_id;
+
+        startup_id = g_getenv ("DESKTOP_AUTOSTART_ID");
+        app_id = "gdm-simple-greeter.desktop";
+
+        error = NULL;
+        res = gsm_manager_call_register_client_sync (sm_proxy, app_id, startup_id, &client_id, NULL, &error);
+        if (! res) {
+                g_warning ("Failed to register client: %s", error->message);
+                g_error_free (error);
+                return FALSE;
+        }
+
+        g_debug ("Client registered with session manager: %s", client_id);
+        client_proxy = gsm_client_private_proxy_new_sync (bus_connection,
+                                                          G_DBUS_PROXY_FLAGS_NONE,
+                                                          SM_DBUS_NAME,
+                                                          client_id,
+                                                          NULL,
+                                                          &error);
+
+        if (client_proxy == NULL) {
+                g_warning ("Failed to track client: %s", error->message);
+                g_error_free (error);
+
+                return FALSE;
+        }
+
+        g_signal_connect (client_proxy,
+                          "stop",
+                          G_CALLBACK (stop_cb),
+                          NULL);
+
+        g_signal_connect (client_proxy,
+                          "query-end-session",
+                          G_CALLBACK (query_end_session_cb),
+                          NULL);
+
+        g_signal_connect (client_proxy,
+                          "end-session",
+                          G_CALLBACK (end_session_cb),
+                          NULL);
+
+        g_unsetenv ("DESKTOP_AUTOSTART_ID");
+
+        return TRUE;
+}
+
+int
+main (int argc, char *argv[])
+{
+        GError            *error;
+        GdmGreeterSession *session;
+        gboolean           res;
+
+        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+        bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+        textdomain (GETTEXT_PACKAGE);
+
+        setlocale (LC_ALL, "");
+
+        gdm_profile_start ("Initializing settings client");
+        if (! gdm_settings_client_init (DATADIR "/gdm/gdm.schemas", "/")) {
+                g_critical ("Unable to initialize settings client");
+                exit (1);
+        }
+        gdm_profile_end ("Initializing settings client");
+
+        g_debug ("Greeter session pid=%d display=%s xauthority=%s",
+                 (int)getpid (),
+                 g_getenv ("DISPLAY"),
+                 g_getenv ("XAUTHORITY"));
+
+        /* FIXME: For testing to make it easier to attach gdb */
+        /*sleep (15);*/
+
+        gdm_log_init ();
+        gdm_log_set_debug (is_debug_set ());
+
+        gtk_init (&argc, &argv);
+
+        g_unix_signal_add (SIGUSR1, on_sigusr1_cb, NULL);
+
+        gdm_profile_start ("Creating new greeter session");
+        session = gdm_greeter_session_new ();
+        if (session == NULL) {
+                g_critical ("Unable to create greeter session");
+                exit (1);
+        }
+        gdm_profile_end ("Creating new greeter session");
+
+        error = NULL;
+        res = gdm_greeter_session_start (session, &error);
+        if (! res) {
+                if (error != NULL) {
+                        g_warning ("Unable to start greeter session: %s", error->message);
+                        g_error_free (error);
+                }
+                exit (1);
+        }
+
+        res = session_manager_connect ();
+        if (! res) {
+                g_warning ("Unable to connect to session manager");
+                exit (1);
+        }
+
+        res = register_client ();
+        if (! res) {
+                g_warning ("Unable to register client with session manager");
+        }
+
+        gtk_main ();
+
+        if (session != NULL) {
+                g_object_unref (session);
+        }
+
+        return 0;
+}
diff --git a/gui/simple-greeter/libgdmsimplegreeter/Makefile.am 
b/gui/simple-greeter/libgdmsimplegreeter/Makefile.am
new file mode 100644
index 0000000..07b55af
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/Makefile.am
@@ -0,0 +1,43 @@
+NULL =
+
+AM_CPPFLAGS = \
+       -I.                                     \
+       -I..                                    \
+       -I$(top_srcdir)/common                  \
+       -DBINDIR=\"$(bindir)\"                  \
+       -DDATADIR=\"$(datadir)\"                \
+       -DLIBDIR=\"$(libdir)\"                  \
+       -DLIBEXECDIR=\"$(libexecdir)\"          \
+       -DLOGDIR=\"$(logdir)\"                  \
+       -DPIXMAPDIR=\"$(pixmapdir)\"            \
+       -DSBINDIR=\"$(sbindir)\"                \
+       $(GTK_CFLAGS)                           \
+       $(NULL)
+
+lib_LTLIBRARIES =                      \
+       libgdmsimplegreeter.la          \
+       $(NULL)
+
+libgdmsimplegreeter_la_SOURCES =               \
+       gdm-login-extension.h                   \
+       gdm-login-extension.c                   \
+       $(NULL)
+
+libgdmsimplegreeter_la_LIBADD =                        \
+       $(GTK_LIBS)                             \
+       $(top_builddir)/common/libgdmcommon.la  \
+       $(NULL)
+
+libgdmsimplegreeter_la_LDFLAGS =               \
+       -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) \
+       -no-undefined                           \
+       $(NULL)
+
+headersdir = $(includedir)/gdm/simple-greeter
+headers_HEADERS = gdm-login-extension.h
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = gdmsimplegreeter.pc
+
+EXTRA_DIST = gdmsimplegreeter.pc
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.c 
b/gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.c
new file mode 100644
index 0000000..fec049c
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Written By: Ray Strode <rstrode redhat com>
+ *
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "gdm-login-extension.h"
+
+enum {
+        ENABLED,
+        DISABLED,
+        ANSWER,
+        USER_CHOSEN,
+        CANCEL,
+        MESSAGE_QUEUE_EMPTY,
+        NUMBER_OF_SIGNALS
+};
+
+static guint signals [NUMBER_OF_SIGNALS] = { 0, };
+
+static void gdm_login_extension_class_init (gpointer g_iface);
+
+GType
+gdm_login_extension_get_type (void)
+{
+        static GType login_extension_type = 0;
+
+        if (!login_extension_type) {
+                login_extension_type = g_type_register_static_simple (G_TYPE_INTERFACE,
+                                                           "GdmLoginExtension",
+                                                           sizeof (GdmLoginExtensionIface),
+                                                           (GClassInitFunc) gdm_login_extension_class_init,
+                                                           0, NULL, 0);
+
+                g_type_interface_add_prerequisite (login_extension_type, G_TYPE_OBJECT);
+        }
+
+        return login_extension_type;
+}
+
+static void
+gdm_login_extension_class_init (gpointer g_iface)
+{
+        GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
+
+        signals [ENABLED] =
+                g_signal_new ("enabled",
+                              iface_type,
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmLoginExtensionIface, enabled),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE,
+                              0);
+        signals [DISABLED] =
+                g_signal_new ("disabled",
+                              iface_type,
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmLoginExtensionIface, disabled),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE,
+                              0);
+        signals [ANSWER] =
+                g_signal_new ("answer",
+                              iface_type,
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmLoginExtensionIface, answer),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__STRING,
+                              G_TYPE_NONE,
+                              1, G_TYPE_STRING);
+        signals [USER_CHOSEN] =
+                g_signal_new ("user-chosen",
+                              iface_type,
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GdmLoginExtensionIface, user_chosen),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_generic,
+                              G_TYPE_BOOLEAN,
+                              1, G_TYPE_STRING);
+        signals [CANCEL] =
+                g_signal_new ("cancel",
+                              iface_type,
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmLoginExtensionIface, cancel),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+        signals [MESSAGE_QUEUE_EMPTY] =
+                g_signal_new ("message-queue-empty",
+                              iface_type,
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (GdmLoginExtensionIface, message_queue_empty),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+}
+
+GIcon *
+gdm_login_extension_get_icon (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_icon (extension);
+}
+
+char *
+gdm_login_extension_get_description (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_description (extension);
+}
+
+char *
+gdm_login_extension_get_name (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_name (extension);
+}
+
+void
+gdm_login_extension_set_enabled (GdmLoginExtension *extension,
+                                   gboolean             should_enable)
+{
+        g_object_set_data (G_OBJECT (extension),
+                           "gdm-greeter-extension-is-disabled",
+                           GINT_TO_POINTER (!should_enable));
+
+        if (should_enable) {
+                g_signal_emit (G_OBJECT (extension), signals [ENABLED], 0);
+        } else {
+                g_signal_emit (G_OBJECT (extension), signals [DISABLED], 0);
+        }
+}
+
+gboolean
+gdm_login_extension_is_enabled (GdmLoginExtension *extension)
+{
+        return !g_object_get_data (G_OBJECT (extension), "gdm-greeter-extension-is-disabled");
+}
+
+gboolean
+gdm_login_extension_is_choosable (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->is_choosable (extension);
+}
+
+gboolean
+gdm_login_extension_is_visible (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->is_visible (extension);
+}
+
+void
+gdm_login_extension_queue_message (GdmLoginExtension   *extension,
+                                     GdmServiceMessageType  type,
+                                     const char            *message)
+{
+        GDM_LOGIN_EXTENSION_GET_IFACE (extension)->queue_message (extension,
+                                                                    type,
+                                                                    message);
+}
+
+void
+gdm_login_extension_ask_question (GdmLoginExtension *extension,
+                                    const char          *message)
+{
+        GDM_LOGIN_EXTENSION_GET_IFACE (extension)->ask_question (extension,
+                                                                   message);
+}
+
+void
+gdm_login_extension_ask_secret (GdmLoginExtension *extension,
+                                  const char          *message)
+{
+        GDM_LOGIN_EXTENSION_GET_IFACE (extension)->ask_secret (extension,
+                                                                 message);
+
+}
+
+void
+gdm_login_extension_reset (GdmLoginExtension *extension)
+{
+        GDM_LOGIN_EXTENSION_GET_IFACE (extension)->reset (extension);
+}
+
+void
+gdm_login_extension_set_ready (GdmLoginExtension *extension)
+{
+        GDM_LOGIN_EXTENSION_GET_IFACE (extension)->set_ready (extension);
+}
+
+gboolean
+gdm_login_extension_focus (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->focus (extension);
+}
+
+char *
+gdm_login_extension_get_service_name (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_service_name (extension);
+
+}
+
+gboolean
+gdm_login_extension_has_queued_messages (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->has_queued_messages (extension);
+}
+
+GtkWidget *
+gdm_login_extension_get_page (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_page (extension);
+}
+
+GtkActionGroup *
+gdm_login_extension_get_actions (GdmLoginExtension *extension)
+{
+        return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_actions (extension);
+}
+
+void
+_gdm_login_extension_emit_answer (GdmLoginExtension *extension,
+                                  const char        *answer)
+{
+        g_signal_emit (extension, signals [ANSWER], 0, answer);
+
+}
+
+void
+_gdm_login_extension_emit_cancel (GdmLoginExtension *extension)
+{
+        g_signal_emit (extension, signals [CANCEL], 0);
+}
+
+gboolean
+_gdm_login_extension_emit_choose_user (GdmLoginExtension *extension,
+                                     const char          *username)
+{
+        gboolean was_chosen;
+
+        was_chosen = FALSE;
+
+        g_signal_emit (extension, signals [USER_CHOSEN], 0, username, &was_chosen);
+
+        return was_chosen;
+}
+
+void
+_gdm_login_extension_emit_message_queue_empty (GdmLoginExtension *extension)
+{
+        g_signal_emit (extension, signals [MESSAGE_QUEUE_EMPTY], 0);
+
+}
diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.h 
b/gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.h
new file mode 100644
index 0000000..7883681
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Written by: Ray Strode
+ */
+
+#ifndef __GDM_LOGIN_EXTENSION_H
+#define __GDM_LOGIN_EXTENSION_H
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GDM_TYPE_LOGIN_EXTENSION         (gdm_login_extension_get_type ())
+#define GDM_LOGIN_EXTENSION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_LOGIN_EXTENSION, 
GdmLoginExtension))
+#define GDM_LOGIN_EXTENSION_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_LOGIN_EXTENSION, 
GdmLoginExtensionClass))
+#define GDM_IS_LOGIN_EXTENSION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_LOGIN_EXTENSION))
+#define GDM_LOGIN_EXTENSION_GET_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), GDM_TYPE_LOGIN_EXTENSION, 
GdmLoginExtensionIface))
+
+#define GDM_LOGIN_EXTENSION_POINT_NAME "gdm-login"
+#define GDM_LOGIN_EXTENSION_DEFAULT_ACTION "default-action"
+#define GDM_LOGIN_EXTENSION_OTHER_USER "__other"
+
+typedef struct _GdmLoginExtension      GdmLoginExtension;
+typedef struct _GdmLoginExtensionIface GdmLoginExtensionIface;
+
+typedef enum {
+        GDM_SERVICE_MESSAGE_TYPE_INFO,
+        GDM_SERVICE_MESSAGE_TYPE_PROBLEM
+} GdmServiceMessageType;
+
+struct _GdmLoginExtensionIface
+{
+        GTypeInterface base_iface;
+
+        /* methods */
+        GIcon *         (* get_icon)             (GdmLoginExtension   *extension);
+        char *          (* get_description)      (GdmLoginExtension   *extension);
+        char *          (* get_name)             (GdmLoginExtension   *extension);
+
+        gboolean        (* is_choosable)         (GdmLoginExtension   *extension);
+        gboolean        (* is_visible)           (GdmLoginExtension   *extension);
+
+        void            (* queue_message)        (GdmLoginExtension   *extension,
+                                                  GdmServiceMessageType  type,
+                                                  const char            *message);
+        void            (* ask_question)         (GdmLoginExtension   *extension,
+                                                  const char            *message);
+        void            (* ask_secret)           (GdmLoginExtension   *extension,
+                                                  const char            *message);
+        void             (* reset)               (GdmLoginExtension   *extension);
+        void             (* set_ready)           (GdmLoginExtension   *extension);
+        char *           (* get_service_name)    (GdmLoginExtension   *extension);
+        GtkWidget *      (* get_page)            (GdmLoginExtension   *extension);
+        GtkActionGroup * (* get_actions)         (GdmLoginExtension   *extension);
+        void             (* request_answer)      (GdmLoginExtension   *extension);
+        gboolean         (* has_queued_messages) (GdmLoginExtension   *extension);
+        gboolean         (* focus)               (GdmLoginExtension   *extension);
+
+        /* signals */
+        void            (* enabled)              (GdmLoginExtension   *extension);
+        void            (* disabled)             (GdmLoginExtension   *extension);
+        char *          (* answer)               (GdmLoginExtension   *extension);
+        void            (* cancel)               (GdmLoginExtension   *extension);
+        gboolean        (* user_chosen)          (GdmLoginExtension   *extension);
+        gboolean        (* message_queue_empty)  (GdmLoginExtension   *extension);
+};
+
+GType    gdm_login_extension_get_type        (void) G_GNUC_CONST;
+
+GIcon   *gdm_login_extension_get_icon        (GdmLoginExtension   *extension);
+char    *gdm_login_extension_get_description (GdmLoginExtension   *extension);
+char    *gdm_login_extension_get_name        (GdmLoginExtension   *extension);
+void     gdm_login_extension_set_enabled     (GdmLoginExtension   *extension,
+                                              gboolean               should_enable);
+gboolean gdm_login_extension_is_enabled      (GdmLoginExtension   *extension);
+gboolean gdm_login_extension_is_choosable    (GdmLoginExtension   *extension);
+gboolean gdm_login_extension_is_visible      (GdmLoginExtension   *extension);
+
+void   gdm_login_extension_queue_message  (GdmLoginExtension   *extension,
+                                           GdmServiceMessageType  type,
+                                           const char            *message);
+
+void   gdm_login_extension_ask_question (GdmLoginExtension   *extension,
+                                         const char            *message);
+
+void   gdm_login_extension_ask_secret   (GdmLoginExtension   *extension,
+                                         const char            *message);
+
+void     gdm_login_extension_reset        (GdmLoginExtension *extension);
+void     gdm_login_extension_set_ready    (GdmLoginExtension *extension);
+gboolean gdm_login_extension_focus        (GdmLoginExtension *extension);
+
+char     *gdm_login_extension_get_service_name    (GdmLoginExtension *extension);
+gboolean  gdm_login_extension_has_queued_messages (GdmLoginExtension *extension);
+
+GtkWidget      *gdm_login_extension_get_page    (GdmLoginExtension *extension);
+GtkActionGroup *gdm_login_extension_get_actions (GdmLoginExtension *extension);
+
+
+/* protected
+ */
+void      _gdm_login_extension_emit_answer           (GdmLoginExtension *extension,
+                                                      const char          *answer);
+void      _gdm_login_extension_emit_cancel           (GdmLoginExtension *extension);
+gboolean  _gdm_login_extension_emit_choose_user      (GdmLoginExtension *extension,
+                                                      const char          *username);
+
+void      _gdm_login_extension_emit_message_queue_empty (GdmLoginExtension *extension);
+
+
+G_END_DECLS
+#endif /* __GDM_LOGIN_EXTENSION_H */
diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdmsimplegreeter.pc.in 
b/gui/simple-greeter/libgdmsimplegreeter/gdmsimplegreeter.pc.in
new file mode 100644
index 0000000..cf8c9ac
--- /dev/null
+++ b/gui/simple-greeter/libgdmsimplegreeter/gdmsimplegreeter.pc.in
@@ -0,0 +1,11 @@
+prefix= prefix@
+exec_prefix= exec_prefix@
+libdir= libdir@
+includedir= includedir@
+extensionsdir= GDM_SIMPLE_GREETER_PLUGINS_DIR@
+
+Name: GDM Simple Greeter
+Description: Library for GDM Simple Greeter Plugins
+Version: @VERSION@
+Libs: -L${libdir} -lgdmsimplegreeter
+Cflags: -I${includedir}/gdm/simple-greeter
diff --git a/gui/simple-greeter/org.gnome.SessionManager.ClientPrivate.xml 
b/gui/simple-greeter/org.gnome.SessionManager.ClientPrivate.xml
new file mode 100644
index 0000000..a167065
--- /dev/null
+++ b/gui/simple-greeter/org.gnome.SessionManager.ClientPrivate.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" 
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd";>
+<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd";>
+  <interface name="org.gnome.SessionManager.ClientPrivate">
+    <method name="EndSessionResponse">
+      <arg name="is_ok" type="b" direction="in">
+        <doc:doc>
+          <doc:summary>Whether or not it is OK to preceed</doc:summary>
+        </doc:doc>
+      </arg>
+      <arg name="reason" type="s" direction="in">
+        <doc:doc>
+          <doc:summary>The reason string</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>This method should be used by the client in response to
+            the <doc:ref type="signal" 
to="org.gnome.SessionManager.ClientPrivate::QueryEndSession">QueryEndSession</doc:ref>
+            and <doc:ref type="signal" 
to="org.gnome.SessionManager.ClientPrivate::EndSession">EndSession</doc:ref> signals.
+          </doc:para>
+        </doc:description>
+      </doc:doc>
+    </method>
+
+    <signal name="Stop">
+      <doc:doc>
+        <doc:summary>Stop client</doc:summary>
+        <doc:description>
+          <doc:para>
+            The client should stop and remove itself from the session in
+            response to this signal.
+          </doc:para>
+        </doc:description>
+      </doc:doc>
+    </signal>
+
+    <signal name="QueryEndSession">
+      <arg name="flags" type="u">
+        <doc:doc>
+          <doc:summary>Flags</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>This signal is used to inform the client that the
+            session is about to end.  The client must respond by
+            calling
+            <doc:ref type="method" 
to="org.gnome.SessionManager.ClientPrivate.EndSessionResponse">EndSessionResponse</doc:ref>
+            within one second of the signal emission.
+          </doc:para>
+          <doc:para>
+            The flags may include:
+            <doc:list>
+              <doc:item>
+                <doc:term>1</doc:term>
+                <doc:definition>Logout is forced.
+                  <doc:ref type="method" 
to="org.gnome.SessionManager.ClientPrivate.EndSessionResponse">EndSessionResponse</doc:ref>
+                reason and any inhibit from client will be
+                ignored.</doc:definition>
+              </doc:item>
+            </doc:list>
+          </doc:para>
+          <doc:para>
+            If the client responds with an EndSessionResponse is-ok
+            argument equal to FALSE and a reason then this reason may
+            be displayed to the user.
+          </doc:para>
+          <doc:para>
+            The client must not attempt to perform any actions or
+            interact with the user in response to this signal.  Any
+            actions required for a clean shutdown should take place in
+            response to the
+            <doc:ref type="signal" 
to="org.gnome.SessionManager.ClientPrivate::EndSession">EndSession</doc:ref> signal.
+          </doc:para>
+          <doc:para>
+            The client should limit operations until either a
+            <doc:ref type="signal" 
to="org.gnome.SessionManager.ClientPrivate::EndSession">EndSession</doc:ref>
+            <doc:ref type="signal" 
to="org.gnome.SessionManager.ClientPrivate::CancelEndSession">CancelEndSession</doc:ref>
+            signal is received.
+          </doc:para>
+        </doc:description>
+      </doc:doc>
+    </signal>
+
+    <signal name="EndSession">
+      <arg name="flags" type="u">
+        <doc:doc>
+          <doc:summary>Flags</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>This signal is used to inform the client that the
+            session is about to end.  The client must respond by
+            calling
+            <doc:ref type="method" 
to="org.gnome.SessionManager.ClientPrivate.EndSessionResponse">EndSessionResponse</doc:ref>
+            within ten seconds of the signal emission.
+          </doc:para>
+          <doc:para>
+            The client must not attempt to interact with the user in
+            response to this signal.  The application will be given a
+            maxium of ten seconds to perform any actions required for
+            a clean shutdown.
+          </doc:para>
+        </doc:description>
+      </doc:doc>
+    </signal>
+
+    <signal name="CancelEndSession">
+      <doc:doc>
+        <doc:description>
+          <doc:para>
+            This signal indicates to the client that a previous emission of
+            <doc:ref type="signal" 
to="org.gnome.SessionManager.ClientPrivate::QueryEndSession">QueryEndSession</doc:ref>
+            has been cancelled.  The client should resume normal operations.
+          </doc:para>
+        </doc:description>
+      </doc:doc>
+    </signal>
+
+  </interface>
+</node>
diff --git a/gui/simple-greeter/org.gnome.SessionManager.xml b/gui/simple-greeter/org.gnome.SessionManager.xml
new file mode 100644
index 0000000..eaf1ef5
--- /dev/null
+++ b/gui/simple-greeter/org.gnome.SessionManager.xml
@@ -0,0 +1,392 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" 
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd";>
+<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd";>
+  <interface name="org.gnome.SessionManager">
+
+    <!-- Initialization phase interfaces -->
+
+    <method name="Setenv">
+      <arg name="variable" type="s" direction="in">
+        <doc:doc>
+          <doc:summary>The variable name</doc:summary>
+        </doc:doc>
+      </arg>
+      <arg name="value" type="s" direction="in">
+        <doc:doc>
+          <doc:summary>The value</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>Adds the variable name to the application launch environment with the specified value.  
May only be used during the Session Manager initialization phase.</doc:para>
+        </doc:description>
+      </doc:doc>
+    </method>
+
+    <method name="InitializationError">
+      <arg name="message" type="s" direction="in">
+        <doc:doc>
+          <doc:summary>The error message</doc:summary>
+        </doc:doc>
+      </arg>
+      <arg name="fatal" type="b" direction="in">
+        <doc:doc>
+          <doc:summary>Whether the error should be treated as fatal</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>May be used by applications launched during the Session Manager initialization phase to 
indicate there was a problem.</doc:para>
+        </doc:description>
+      </doc:doc>
+    </method>
+
+    <!-- Running phase interfaces -->
+
+    <method name="RegisterClient">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg type="s" name="app_id" direction="in">
+        <doc:doc>
+          <doc:summary>The application identifier</doc:summary>
+        </doc:doc>
+      </arg>
+      <arg type="s" name="client_startup_id" direction="in">
+        <doc:doc>
+          <doc:summary>Client startup identifier</doc:summary>
+        </doc:doc>
+      </arg>
+      <arg type="o" name="client_id" direction="out">
+        <doc:doc>
+          <doc:summary>The object path of the newly registered client</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>Register the caller as a Session Management client.</doc:para>
+        </doc:description>
+      </doc:doc>
+    </method>
+
+    <method name="UnregisterClient">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg type="o" name="client_id" direction="in">
+        <doc:doc>
+          <doc:summary>The object path of the client</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>Unregister the specified client from Session Management.</doc:para>
+        </doc:description>
+      </doc:doc>
+    </method>
+
+    <method name="Inhibit">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg type="s" name="app_id" direction="in">
+        <doc:doc>
+          <doc:summary>The application identifier</doc:summary>
+        </doc:doc>
+      </arg>
+      <arg type="u" name="toplevel_xid" direction="in">
+        <doc:doc>
+          <doc:summary>The toplevel X window identifier</doc:summary>
+        </doc:doc>
+      </arg>
+      <arg type="s" name="reason" direction="in">
+        <doc:doc>
+          <doc:summary>The reason for the inhibit</doc:summary>
+        </doc:doc>
+      </arg>
+      <arg type="u" name="flags" direction="in">
+        <doc:doc>
+          <doc:summary>Flags that spefify what should be inhibited</doc:summary>
+        </doc:doc>
+      </arg>
+      <arg type="u" name="inhibit_cookie" direction="out">
+        <doc:doc>
+          <doc:summary>The cookie</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:summary>
+          Proactively indicates that the calling application is performing an action that should not be 
interrupted and sets a reason to be displayed to the user when an interruption is about to take placea.
+        </doc:summary>
+        <doc:description>
+          <doc:para>Applications should invoke this method when they begin an operation that
+            should not be interrupted, such as creating a CD or DVD.  The types of actions
+            that may be blocked are specified by the flags parameter.  When the application
+            completes the operation it should call <doc:ref type="method" 
to="org.gnome.SessionManager.Uninhibit">Uninhibit()</doc:ref>
+            or disconnect from the session bus.
+          </doc:para>
+          <doc:para>
+            Applications should not expect that they will always be able to block the
+            action.  In most cases, users will be given the option to force the action
+            to take place.
+          </doc:para>
+          <doc:para>
+            Reasons should be short and to the point.
+          </doc:para>
+          <doc:para>
+            The flags parameter must include at least one of the following:
+            <doc:list>
+              <doc:item>
+                <doc:term>1</doc:term>
+                <doc:definition>Inhibit logging out</doc:definition>
+              </doc:item>
+              <doc:item>
+                <doc:term>2</doc:term>
+                <doc:definition>Inhibit user switching</doc:definition>
+              </doc:item>
+              <doc:item>
+                <doc:term>4</doc:term>
+                <doc:definition>Inhibit suspending the session or computer</doc:definition>
+              </doc:item>
+              <doc:item>
+                <doc:term>8</doc:term>
+                <doc:definition>Inhibit the session being marked as idle</doc:definition>
+              </doc:item>
+            </doc:list>
+            Values for flags may be bitwise or'ed together.
+          </doc:para>
+          <doc:para>
+            The returned cookie is used to uniquely identify this request.  It should be used
+            as an argument to <doc:ref type="method" 
to="org.gnome.SessionManager.Uninhibit">Uninhibit()</doc:ref> in
+            order to remove the request.
+          </doc:para>
+        </doc:description>
+      </doc:doc>
+    </method>
+
+    <method name="Uninhibit">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg type="u" name="inhibit_cookie" direction="in">
+        <doc:doc>
+          <doc:summary>The cookie</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>Cancel a previous call to <doc:ref type="method" 
to="org.gnome.SessionManager.Inhibit">Inhibit()</doc:ref> identified by the cookie.</doc:para>
+        </doc:description>
+      </doc:doc>
+    </method>
+
+    <method name="IsInhibited">
+      <arg type="u" name="flags" direction="in">
+        <doc:doc>
+          <doc:summary>Flags that spefify what should be inhibited</doc:summary>
+        </doc:doc>
+      </arg>
+      <arg type="b" name="is_inhibited" direction="out">
+        <doc:doc>
+          <doc:summary>Returns TRUE if any of the operations in the bitfield flags are 
inhibited</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>Determine if operation(s) specified by the flags
+            are currently inhibited.  Flags are same as those accepted
+            by the
+            <doc:ref type="method" to="org.gnome.SessionManager.Inhibit">Inhibit()</doc:ref>
+            method.</doc:para>
+        </doc:description>
+      </doc:doc>
+    </method>
+
+    <method name="GetClients">
+      <arg name="clients" direction="out" type="ao">
+        <doc:doc>
+          <doc:summary>an array of client IDs</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>This gets a list of all the <doc:ref type="interface" 
to="org.gnome.SessionManager.Client">Clients</doc:ref>
+          that are currently known to the session manager.</doc:para>
+          <doc:para>Each Client ID is an D-Bus object path for the object that implements the
+          <doc:ref type="interface" to="org.gnome.SessionManager.Client">Client</doc:ref> 
interface.</doc:para>
+        </doc:description>
+        <doc:seealso><doc:ref type="interface" 
to="org.gnome.SessionManager.Client">org.gnome.SessionManager.Client</doc:ref></doc:seealso>
+      </doc:doc>
+    </method>
+
+    <method name="GetInhibitors">
+      <arg name="inhibitors" direction="out" type="ao">
+        <doc:doc>
+          <doc:summary>an array of inhibitor IDs</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>This gets a list of all the <doc:ref type="interface" 
to="org.gnome.SessionManager.Inhibitor">Inhibitors</doc:ref>
+          that are currently known to the session manager.</doc:para>
+          <doc:para>Each Inhibitor ID is an D-Bus object path for the object that implements the
+          <doc:ref type="interface" to="org.gnome.SessionManager.Inhibitor">Inhibitor</doc:ref> 
interface.</doc:para>
+        </doc:description>
+        <doc:seealso><doc:ref type="interface" 
to="org.gnome.SessionManager.Inhibitor">org.gnome.SessionManager.Inhibitor</doc:ref></doc:seealso>
+      </doc:doc>
+    </method>
+
+
+    <method name="IsAutostartConditionHandled">
+      <arg name="condition" direction="in" type="s">
+        <doc:doc>
+          <doc:summary>The autostart condition string</doc:summary>
+        </doc:doc>
+      </arg>
+      <arg name="handled" direction="out" type="b">
+        <doc:doc>
+          <doc:summary>True if condition is handled, false otherwise</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>Allows the caller to determine whether the session manager is
+          handling changes to the specified autostart condition.</doc:para>
+        </doc:description>
+      </doc:doc>
+    </method>
+
+    <method name="Shutdown">
+      <doc:doc>
+        <doc:description>
+          <doc:para>Request a shutdown dialog</doc:para>
+        </doc:description>
+      </doc:doc>
+    </method>
+
+    <method name="CanShutdown">
+      <arg name="is_available" direction="out" type="b">
+        <doc:doc>
+          <doc:summary>True if shutdown is available to the user, false otherwise</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>Allows the caller to determine whether or not it's okay to show
+          a shutdown option in the UI</doc:para>
+        </doc:description>
+      </doc:doc>
+    </method>
+
+    <method name="Logout">
+      <arg name="mode" type="u" direction="in">
+        <doc:doc>
+          <doc:summary>The type of logout that is being requested</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>Request a logout dialog</doc:para>
+          <doc:para>
+            Allowed values for the mode parameter are:
+            <doc:list>
+              <doc:item>
+                <doc:term>0</doc:term>
+                <doc:definition>Normal.</doc:definition>
+              </doc:item>
+              <doc:item>
+                <doc:term>1</doc:term>
+                <doc:definition>No confirmation inferface should be shown.</doc:definition>
+              </doc:item>
+              <doc:item>
+                <doc:term>2</doc:term>
+                <doc:definition>Forcefully logout.  No confirmation will be shown and any inhibitors will be 
ignored.</doc:definition>
+              </doc:item>
+            </doc:list>
+            Values for flags may be bitwise or'ed together.
+          </doc:para>
+        </doc:description>
+      </doc:doc>
+    </method>
+
+    <method name="IsSessionRunning">
+      <arg name="running" direction="out" type="b">
+        <doc:doc>
+          <doc:summary>True if the session has entered the Running phase, false otherwise</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>Allows the caller to determine whether the session manager
+          has entered the Running phase, in case the client missed the
+          SessionRunning signal.</doc:para>
+        </doc:description>
+      </doc:doc>
+    </method>
+
+    <!-- Signals -->
+
+    <signal name="ClientAdded">
+      <arg name="id" type="o">
+        <doc:doc>
+          <doc:summary>The object path for the added client</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>Emitted when a client has been added to the session manager.
+          </doc:para>
+        </doc:description>
+      </doc:doc>
+    </signal>
+    <signal name="ClientRemoved">
+      <arg name="id" type="o">
+        <doc:doc>
+          <doc:summary>The object path for the removed client</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>Emitted when a client has been removed from the session manager.
+          </doc:para>
+        </doc:description>
+      </doc:doc>
+    </signal>
+
+    <signal name="InhibitorAdded">
+      <arg name="id" type="o">
+        <doc:doc>
+          <doc:summary>The object path for the added inhibitor</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>Emitted when an inhibitor has been added to the session manager.
+          </doc:para>
+        </doc:description>
+      </doc:doc>
+    </signal>
+    <signal name="InhibitorRemoved">
+      <arg name="id" type="o">
+        <doc:doc>
+          <doc:summary>The object path for the removed inhibitor</doc:summary>
+        </doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>Emitted when an inhibitor has been removed from the session manager.
+          </doc:para>
+        </doc:description>
+      </doc:doc>
+    </signal>
+
+    <signal name="SessionRunning">
+      <doc:doc>
+        <doc:description>
+          <doc:para>Indicates the session has entered the Running phase.</doc:para>
+        </doc:description>
+      </doc:doc>
+    </signal>
+
+    <signal name="SessionOver">
+      <doc:doc>
+        <doc:description>
+          <doc:para>Indicates the session is about to end.</doc:para>
+        </doc:description>
+      </doc:doc>
+    </signal>
+
+  </interface>
+</node>
diff --git a/gui/simple-greeter/test-a11y-preferences.c b/gui/simple-greeter/test-a11y-preferences.c
new file mode 100644
index 0000000..dfe2960
--- /dev/null
+++ b/gui/simple-greeter/test-a11y-preferences.c
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 William Jon McCann <jmccann 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <libintl.h>
+#include <locale.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gdm-a11y-preferences-dialog.h"
+
+int
+main (int argc, char *argv[])
+{
+        GtkWidget *dialog;
+
+        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+        bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+        textdomain (GETTEXT_PACKAGE);
+
+        setlocale (LC_ALL, "");
+
+        gtk_init (&argc, &argv);
+
+        dialog = gdm_a11y_preferences_dialog_new ();
+        /*gtk_widget_set_size_request (dialog, 480, 128);*/
+
+        gtk_dialog_run (GTK_DIALOG (dialog));
+
+        gtk_widget_destroy (dialog);
+
+        return 0;
+}
diff --git a/gui/simple-greeter/test-filesystem-type.c b/gui/simple-greeter/test-filesystem-type.c
new file mode 100644
index 0000000..30b795f
--- /dev/null
+++ b/gui/simple-greeter/test-filesystem-type.c
@@ -0,0 +1,107 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <libintl.h>
+#include <locale.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+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: %s", 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));
+
+        g_object_unref (file);
+        g_object_unref (file_info);
+
+        return filesystem_type;
+}
+
+static void
+print_fstype (char **paths)
+{
+        int i;
+
+        i = 0;
+        while (paths[i] != NULL) {
+                char *fstype;
+                fstype = get_filesystem_type (paths[i]);
+                g_print ("%s is %s\n", paths[i], fstype);
+                g_free (fstype);
+                i++;
+        }
+}
+
+int
+main (int argc, char *argv[])
+{
+        GOptionContext *ctx;
+        char          **paths = NULL;
+        GOptionEntry    options[] = {
+                {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &paths, NULL},
+                {NULL}
+        };
+
+        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+        bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+        textdomain (GETTEXT_PACKAGE);
+
+        setlocale (LC_ALL, "");
+
+        /* Option parsing */
+        ctx = g_option_context_new ("- Test filesystem type");
+        g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE);
+        g_option_context_parse (ctx, &argc, &argv, NULL);
+        g_option_context_free (ctx);
+
+        if (paths != NULL) {
+                print_fstype (paths);
+
+                g_strfreev (paths);
+        }
+
+        return 0;
+}
diff --git a/gui/simple-greeter/test-greeter-background.c b/gui/simple-greeter/test-greeter-background.c
new file mode 100644
index 0000000..03db0a2
--- /dev/null
+++ b/gui/simple-greeter/test-greeter-background.c
@@ -0,0 +1,54 @@
+/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <libintl.h>
+#include <locale.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gdm-greeter-background.h"
+
+int
+main (int argc, char *argv[])
+{
+        GtkWidget        *background;
+
+        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+        bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+        textdomain (GETTEXT_PACKAGE);
+
+        setlocale (LC_ALL, "");
+
+        gtk_init (&argc, &argv);
+
+        background = gdm_greeter_background_new ();
+        gtk_widget_show_all (background);
+
+        gtk_main ();
+
+        return 0;
+}
diff --git a/gui/simple-greeter/test-greeter-login-window.c b/gui/simple-greeter/test-greeter-login-window.c
new file mode 100644
index 0000000..dda6137
--- /dev/null
+++ b/gui/simple-greeter/test-greeter-login-window.c
@@ -0,0 +1,121 @@
+/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <libintl.h>
+#include <locale.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gdm-settings-client.h"
+
+#include "gdm-greeter-login-window.h"
+
+static guint cancel_idle_id = 0;
+
+static gboolean     timed_login   = FALSE;
+static GOptionEntry entries []   = {
+        { "timed-login", 0, 0, G_OPTION_ARG_NONE, &timed_login, "Test timed login", NULL },
+        { NULL }
+};
+
+static gboolean
+do_cancel (GdmGreeterLoginWindow *login_window)
+{
+        gdm_greeter_login_window_reset (GDM_GREETER_LOGIN_WINDOW (login_window));
+        cancel_idle_id = 0;
+        return FALSE;
+}
+
+static void
+on_select_user (GdmGreeterLoginWindow *login_window,
+                const char            *text,
+                gpointer               data)
+{
+        g_debug ("user selected: %s", text);
+        if (cancel_idle_id != 0) {
+                return;
+        }
+        cancel_idle_id = g_timeout_add_seconds (5, (GSourceFunc) do_cancel, login_window);
+}
+
+static void
+on_cancelled (GdmGreeterLoginWindow *login_window,
+              gpointer               data)
+{
+        g_debug ("login cancelled");
+        if (cancel_idle_id != 0) {
+                g_source_remove (cancel_idle_id);
+                cancel_idle_id = 0;
+        }
+
+        gdm_greeter_login_window_reset (login_window);
+}
+
+int
+main (int argc, char *argv[])
+{
+        GtkWidget        *login_window;
+
+        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+        bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+        textdomain (GETTEXT_PACKAGE);
+
+        setlocale (LC_ALL, "");
+
+        gtk_init_with_args (&argc,
+                            &argv,
+                            "",
+                            entries,
+                            NULL,
+                            NULL);
+
+        if (! gdm_settings_client_init (DATADIR "/gdm/gdm.schemas", "/")) {
+                g_critical ("Unable to initialize settings client");
+                exit (1);
+        }
+
+        login_window = gdm_greeter_login_window_new (TRUE);
+        g_signal_connect (login_window,
+                          "user-selected",
+                          G_CALLBACK (on_select_user),
+                          NULL);
+        g_signal_connect (login_window,
+                          "cancelled",
+                          G_CALLBACK (on_cancelled),
+                          NULL);
+        if (timed_login) {
+                gdm_greeter_login_window_request_timed_login (GDM_GREETER_LOGIN_WINDOW (login_window),
+                                                              g_get_user_name (),
+                                                              60);
+        }
+
+        gtk_widget_show (login_window);
+
+        gtk_main ();
+
+        return 0;
+}
diff --git a/gui/simple-greeter/test-greeter-panel.c b/gui/simple-greeter/test-greeter-panel.c
new file mode 100644
index 0000000..bac37b7
--- /dev/null
+++ b/gui/simple-greeter/test-greeter-panel.c
@@ -0,0 +1,63 @@
+/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <libintl.h>
+#include <locale.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gdm-greeter-panel.h"
+
+int
+main (int argc, char *argv[])
+{
+        GtkWidget  *panel;
+        GdkDisplay *display;
+        GdkScreen  *screen;
+        int         monitor;
+        int         x, y;
+
+        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+        bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+        textdomain (GETTEXT_PACKAGE);
+
+        setlocale (LC_ALL, "");
+
+        gtk_init (&argc, &argv);
+
+        display = gdk_display_get_default ();
+        gdk_display_get_pointer (display, &screen, &x, &y, NULL);
+        monitor = gdk_screen_get_monitor_at_point (screen, x, y);
+
+        panel = gdm_greeter_panel_new (screen, monitor, TRUE);
+
+        gtk_widget_show (panel);
+
+        gtk_main ();
+
+        return 0;
+}
diff --git a/gui/simple-greeter/test-languages.c b/gui/simple-greeter/test-languages.c
new file mode 100644
index 0000000..e33d608
--- /dev/null
+++ b/gui/simple-greeter/test-languages.c
@@ -0,0 +1,81 @@
+/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <libintl.h>
+#include <locale.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gdm-languages.h"
+
+static void
+print_languages (void)
+{
+        char **language_names;
+        int    i;
+
+        language_names = gdm_get_all_language_names ();
+
+        for (i = 0; language_names[i] != NULL; i++) {
+                char *language;
+                char *normalized_name;
+                char *readable_language;
+
+                normalized_name = gdm_normalize_language_name (language_names[i]);
+                language = gdm_get_language_from_name (normalized_name, normalized_name);
+                readable_language = gdm_get_language_from_name (normalized_name, NULL);
+
+                g_print ("%s\t%s\t%s\t%s\n",
+                         language_names[i],
+                         normalized_name,
+                         language,
+                         readable_language);
+
+                g_free (language);
+                g_free (readable_language);
+                g_free (normalized_name);
+        }
+
+        g_strfreev (language_names);
+}
+
+int
+main (int argc, char *argv[])
+{
+
+        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+        bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+        textdomain (GETTEXT_PACKAGE);
+
+        setlocale (LC_ALL, "");
+
+        gtk_init (&argc, &argv);
+
+        print_languages ();
+
+        return 0;
+}
diff --git a/gui/simple-greeter/test-remote-login-window.c b/gui/simple-greeter/test-remote-login-window.c
new file mode 100644
index 0000000..d0096a9
--- /dev/null
+++ b/gui/simple-greeter/test-remote-login-window.c
@@ -0,0 +1,100 @@
+/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <libintl.h>
+#include <locale.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gdm-remote-login-window.h"
+
+int
+main (int argc, char *argv[])
+{
+        GtkWidget        *login_window;
+        char             *std_out;
+        char             *hostname;
+        GRegex           *re;
+        GMatchInfo       *match_info = NULL;
+        gboolean          res;
+        GError           *error;
+
+        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+        bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+        textdomain (GETTEXT_PACKAGE);
+
+        std_out = NULL;
+        g_spawn_command_line_sync (LIBEXECDIR "/gdm-host-chooser",
+                                   &std_out,
+                                   NULL, NULL, NULL);
+        if (std_out == NULL) {
+                exit (1);
+        }
+
+        error = NULL;
+        re = g_regex_new ("hostname: (?P<hostname>[a-zA-Z0-9.-]+)", 0, 0, &error);
+        if (re == NULL) {
+                g_warning ("%s", error->message);
+                goto out;
+        }
+
+        g_regex_match (re, std_out, 0, &match_info);
+
+        res = g_match_info_matches (match_info);
+        if (! res) {
+                g_warning ("Unable to parse output: %s", std_out);
+                goto out;
+        }
+
+        hostname = g_match_info_fetch_named (match_info, "hostname");
+
+        g_debug ("Got %s", hostname);
+
+        setlocale (LC_ALL, "");
+
+        gtk_init (&argc, &argv);
+
+        login_window = gdm_remote_login_window_new (TRUE);
+        g_signal_connect (login_window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
+        gtk_widget_show (login_window);
+
+        gdm_remote_login_window_connect (GDM_REMOTE_LOGIN_WINDOW (login_window), hostname);
+
+        gtk_main ();
+ out:
+
+        if (match_info != NULL) {
+                g_match_info_free (match_info);
+        }
+        if (re != NULL) {
+                g_regex_unref (re);
+        }
+
+        g_free (std_out);
+
+        return 0;
+}
diff --git a/gui/simple-greeter/test-sessions.c b/gui/simple-greeter/test-sessions.c
new file mode 100644
index 0000000..71de1b0
--- /dev/null
+++ b/gui/simple-greeter/test-sessions.c
@@ -0,0 +1,81 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <libintl.h>
+#include <locale.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gdm-sessions.h"
+
+static void
+print_sessions (void)
+{
+        char **session_names;
+        int    i;
+
+        session_names = gdm_get_all_sessions ();
+
+        for (i = 0; session_names[i] != NULL; i++) {
+                gboolean res;
+                char    *name;
+                char    *comment;
+
+                res = gdm_get_details_for_session (session_names[i],
+                                                   &name,
+                                                   &comment);
+                if (! res) {
+                        continue;
+                }
+                g_print ("%s\t%s\t%s\n",
+                         session_names[i],
+                         name,
+                         comment);
+
+                g_free (name);
+                g_free (comment);
+        }
+
+        g_strfreev (session_names);
+}
+
+int
+main (int argc, char *argv[])
+{
+
+        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+        bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+        textdomain (GETTEXT_PACKAGE);
+
+        setlocale (LC_ALL, "");
+
+        gtk_init (&argc, &argv);
+
+        print_sessions ();
+
+        return 0;
+}
diff --git a/gui/simple-greeter/test-user-chooser.c b/gui/simple-greeter/test-user-chooser.c
new file mode 100644
index 0000000..727163f
--- /dev/null
+++ b/gui/simple-greeter/test-user-chooser.c
@@ -0,0 +1,69 @@
+/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <libintl.h>
+#include <locale.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gdm-user-chooser-dialog.h"
+#include "gdm-settings-client.h"
+
+int
+main (int argc, char *argv[])
+{
+        GtkWidget *dialog;
+
+        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+        bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+        textdomain (GETTEXT_PACKAGE);
+
+        setlocale (LC_ALL, "");
+
+        gtk_init (&argc, &argv);
+
+        if (! gdm_settings_client_init (DATADIR "/gdm/gdm.schemas", "/")) {
+                g_critical ("Unable to initialize settings client");
+                exit (1);
+        }
+
+        dialog = gdm_user_chooser_dialog_new ();
+        /*gtk_widget_set_size_request (dialog, 480, 128);*/
+        gdm_user_chooser_dialog_set_show_user_guest (GDM_USER_CHOOSER_DIALOG (dialog), TRUE);
+        gdm_user_chooser_dialog_set_show_user_auto (GDM_USER_CHOOSER_DIALOG (dialog), TRUE);
+
+        if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
+                char *name;
+
+                name = gdm_user_chooser_dialog_get_chosen_user_name (GDM_USER_CHOOSER_DIALOG (dialog));
+                g_message ("User: %s", name ? name : "(null)");
+                g_free (name);
+        }
+        gtk_widget_destroy (dialog);
+
+        return 0;
+}
diff --git a/gui/simple-greeter/test-user-manager.c b/gui/simple-greeter/test-user-manager.c
new file mode 100644
index 0000000..71d7514
--- /dev/null
+++ b/gui/simple-greeter/test-user-manager.c
@@ -0,0 +1,148 @@
+/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <libintl.h>
+#include <locale.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include <act/act-user-manager.h>
+#include <act/act-user.h>
+
+#include "gdm-settings-client.h"
+
+static ActUserManager *manager = NULL;
+static GMainLoop      *main_loop = NULL;
+
+static gboolean     do_monitor       = FALSE;
+static gboolean     fatal_warnings   = FALSE;
+static GOptionEntry entries []   = {
+        { "fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &fatal_warnings, "Make all warnings fatal", NULL },
+        { "monitor", 0, 0, G_OPTION_ARG_NONE, &do_monitor, "Monitor changes", NULL },
+        { NULL }
+};
+
+static void
+on_is_loaded_changed (ActUserManager *manager,
+                      GParamSpec     *pspec,
+                      gpointer        data)
+{
+        GSList *users;
+
+        g_debug ("Users loaded");
+
+        users = act_user_manager_list_users (manager);
+        while (users != NULL) {
+                g_print ("User: %s\n", act_user_get_user_name (users->data));
+                users = g_slist_delete_link (users, users);
+        }
+
+        if (! do_monitor) {
+                g_main_loop_quit (main_loop);
+        }
+}
+
+static void
+on_user_added (ActUserManager *manager,
+               ActUser        *user,
+               gpointer        data)
+{
+        g_debug ("User added: %s", act_user_get_user_name (user));
+}
+
+static void
+on_user_removed (ActUserManager *manager,
+                 ActUser        *user,
+                 gpointer        data)
+{
+        g_debug ("User removed: %s", act_user_get_user_name (user));
+}
+
+int
+main (int argc, char *argv[])
+{
+        GOptionContext *context;
+        GError         *error;
+        gboolean        res;
+
+        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+        bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+        textdomain (GETTEXT_PACKAGE);
+
+        setlocale (LC_ALL, "");
+
+        context = g_option_context_new ("GNOME Display Manager");
+        g_option_context_add_main_entries (context, entries, NULL);
+        g_option_context_set_ignore_unknown_options (context, TRUE);
+
+        error = NULL;
+        res = g_option_context_parse (context, &argc, &argv, &error);
+        g_option_context_free (context);
+        if (! res) {
+                g_warning ("%s", error->message);
+                g_error_free (error);
+                return 0;
+        }
+
+        if (fatal_warnings) {
+                GLogLevelFlags fatal_mask;
+
+                fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK);
+                fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL;
+                g_log_set_always_fatal (fatal_mask);
+        }
+
+        if (! gdm_settings_client_init (DATADIR "/gdm/gdm.schemas", "/")) {
+                g_critical ("Unable to initialize settings client");
+                exit (1);
+        }
+
+        manager = act_user_manager_get_default ();
+        g_object_set (manager, "include-all", TRUE, NULL);
+        g_signal_connect (manager,
+                          "notify::is-loaded",
+                          G_CALLBACK (on_is_loaded_changed),
+                          NULL);
+        g_signal_connect (manager,
+                          "user-added",
+                          G_CALLBACK (on_user_added),
+                          NULL);
+        g_signal_connect (manager,
+                          "user-removed",
+                          G_CALLBACK (on_user_removed),
+                          NULL);
+
+        main_loop = g_main_loop_new (NULL, FALSE);
+
+        g_main_loop_run (main_loop);
+        if (main_loop != NULL) {
+                g_main_loop_unref (main_loop);
+        }
+        g_object_unref (manager);
+
+        return 0;
+}



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