From 1732d1c22e88d6f05f5b5e234990ce68b0e59cc0 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Mon, 17 Jun 2013 07:54:40 -0400 Subject: 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(-) create mode 100644 gui/simple-greeter/Makefile.am create mode 100644 gui/simple-greeter/extensions/Makefile.am create mode 100644 gui/simple-greeter/extensions/fingerprint/Makefile.am create mode 100644 gui/simple-greeter/extensions/fingerprint/gdm-fingerprint-extension.c create mode 100644 gui/simple-greeter/extensions/fingerprint/gdm-fingerprint-extension.h create mode 100644 gui/simple-greeter/extensions/fingerprint/icons/16x16/Makefile.am create mode 100644 gui/simple-greeter/extensions/fingerprint/icons/16x16/gdm-fingerprint.png create mode 100644 gui/simple-greeter/extensions/fingerprint/icons/48x48/Makefile.am create mode 100644 gui/simple-greeter/extensions/fingerprint/icons/48x48/gdm-fingerprint.png create mode 100644 gui/simple-greeter/extensions/fingerprint/icons/Makefile.am create mode 100644 gui/simple-greeter/extensions/fingerprint/page.ui create mode 100644 gui/simple-greeter/extensions/password/Makefile.am create mode 100644 gui/simple-greeter/extensions/password/gdm-password-extension.c create mode 100644 gui/simple-greeter/extensions/password/gdm-password-extension.h create mode 100644 gui/simple-greeter/extensions/password/page.ui create mode 100644 gui/simple-greeter/extensions/smartcard/Makefile.am create mode 100644 gui/simple-greeter/extensions/smartcard/gdm-smartcard create mode 100644 gui/simple-greeter/extensions/smartcard/gdm-smartcard-extension.c create mode 100644 gui/simple-greeter/extensions/smartcard/gdm-smartcard-extension.h create mode 100644 gui/simple-greeter/extensions/smartcard/gdm-smartcard-manager.c create mode 100644 gui/simple-greeter/extensions/smartcard/gdm-smartcard-manager.h create mode 100644 gui/simple-greeter/extensions/smartcard/gdm-smartcard-worker.c create mode 100644 gui/simple-greeter/extensions/smartcard/gdm-smartcard.c create mode 100644 gui/simple-greeter/extensions/smartcard/gdm-smartcard.h create mode 100644 gui/simple-greeter/extensions/smartcard/icons/16x16/Makefile.am create mode 100644 gui/simple-greeter/extensions/smartcard/icons/16x16/gdm-smartcard.png create mode 100644 gui/simple-greeter/extensions/smartcard/icons/48x48/Makefile.am create mode 100644 gui/simple-greeter/extensions/smartcard/icons/48x48/gdm-smartcard.png create mode 100644 gui/simple-greeter/extensions/smartcard/icons/Makefile.am create mode 100644 gui/simple-greeter/extensions/smartcard/page.ui create mode 100644 gui/simple-greeter/extensions/unified/Makefile.am create mode 100644 gui/simple-greeter/extensions/unified/gdm-unified-extension.c create mode 100644 gui/simple-greeter/extensions/unified/gdm-unified-extension.h create mode 100644 gui/simple-greeter/extensions/unified/gdm.pam create mode 100644 gui/simple-greeter/extensions/unified/page.ui create mode 100644 gui/simple-greeter/gdm-cell-renderer-timer.c create mode 100644 gui/simple-greeter/gdm-cell-renderer-timer.h create mode 100644 gui/simple-greeter/gdm-chooser-widget.c create mode 100644 gui/simple-greeter/gdm-chooser-widget.h create mode 100644 gui/simple-greeter/gdm-clock-widget.c create mode 100644 gui/simple-greeter/gdm-clock-widget.h create mode 100644 gui/simple-greeter/gdm-extension-list.c create mode 100644 gui/simple-greeter/gdm-extension-list.h create mode 100644 gui/simple-greeter/gdm-greeter-login-window.c create mode 100644 gui/simple-greeter/gdm-greeter-login-window.h create mode 100644 gui/simple-greeter/gdm-greeter-login-window.ui create mode 100644 gui/simple-greeter/gdm-greeter-panel.c create mode 100644 gui/simple-greeter/gdm-greeter-panel.h create mode 100644 gui/simple-greeter/gdm-greeter-session.c create mode 100644 gui/simple-greeter/gdm-greeter-session.h create mode 100644 gui/simple-greeter/gdm-option-widget.c create mode 100644 gui/simple-greeter/gdm-option-widget.h create mode 100644 gui/simple-greeter/gdm-remote-login-window.c create mode 100644 gui/simple-greeter/gdm-remote-login-window.h create mode 100644 gui/simple-greeter/gdm-scrollable-widget.c create mode 100644 gui/simple-greeter/gdm-scrollable-widget.h create mode 100644 gui/simple-greeter/gdm-session-option-widget.c create mode 100644 gui/simple-greeter/gdm-session-option-widget.h create mode 100644 gui/simple-greeter/gdm-sessions.c create mode 100644 gui/simple-greeter/gdm-sessions.h create mode 100644 gui/simple-greeter/gdm-timer.c create mode 100644 gui/simple-greeter/gdm-timer.h create mode 100644 gui/simple-greeter/gdm-user-chooser-dialog.c create mode 100644 gui/simple-greeter/gdm-user-chooser-dialog.h create mode 100644 gui/simple-greeter/gdm-user-chooser-widget.c create mode 100644 gui/simple-greeter/gdm-user-chooser-widget.h create mode 100644 gui/simple-greeter/gdm-user-private.h create mode 100644 gui/simple-greeter/greeter-main.c create mode 100644 gui/simple-greeter/libgdmsimplegreeter/Makefile.am create mode 100644 gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.c create mode 100644 gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.h create mode 100644 gui/simple-greeter/libgdmsimplegreeter/gdmsimplegreeter.pc.in create mode 100644 gui/simple-greeter/org.gnome.SessionManager.ClientPrivate.xml create mode 100644 gui/simple-greeter/org.gnome.SessionManager.xml create mode 100644 gui/simple-greeter/test-a11y-preferences.c create mode 100644 gui/simple-greeter/test-filesystem-type.c create mode 100644 gui/simple-greeter/test-greeter-background.c create mode 100644 gui/simple-greeter/test-greeter-login-window.c create mode 100644 gui/simple-greeter/test-greeter-panel.c create mode 100644 gui/simple-greeter/test-languages.c create mode 100644 gui/simple-greeter/test-remote-login-window.c create mode 100644 gui/simple-greeter/test-sessions.c create mode 100644 gui/simple-greeter/test-user-chooser.c create mode 100644 gui/simple-greeter/test-user-manager.c diff --git a/configure.ac b/configure.ac index d9e89a9d..42c186cd 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 81eb8ef4..3d7bf3ba 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 fe6976e6..1def3b40 100644 --- a/docs/C/index.docbook +++ b/docs/C/index.docbook @@ -116,7 +116,7 @@ - Greeter - The graphical login window (provided by gnome-shell). + Greeter - The graphical login window (gdm-simple-greeter). @@ -385,7 +385,7 @@ 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 04da1050..ce900551 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 00000000..138114ba --- /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 00000000..2cba7ec9 --- /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 00000000..3466c87d --- /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 00000000..29aca45c --- /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 + * + */ + +#include +#include + +#include "gdm-fingerprint-extension.h" + +#include +#include +#include + +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 00000000..e6cba4eb --- /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 + */ + +#ifndef __GDM_FINGERPRINT_EXTENSION_H +#define __GDM_FINGERPRINT_EXTENSION_H + +#include +#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 00000000..f42e3170 --- /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 00000000..4438cee2 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 00000000..f4ab2a0f --- /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 00000000..fd6f546c 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 00000000..c20f10d0 --- /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 00000000..8fa5c7be --- /dev/null +++ b/gui/simple-greeter/extensions/fingerprint/page.ui @@ -0,0 +1,57 @@ + + + + + True + vertical + + + True + 6 + + + True + + + False + False + 0 + + + + + True + True + True + + + 1 + + + + + True + True + 0 + + + + + True + + + True + + + 0 + + + + + True + True + 1 + + + + diff --git a/gui/simple-greeter/extensions/password/Makefile.am b/gui/simple-greeter/extensions/password/Makefile.am new file mode 100644 index 00000000..dd3f75bc --- /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 00000000..6e1fc9c8 --- /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 + * + */ + +#include +#include "gdm-password-extension.h" +#include "gdm-login-extension.h" + +#include +#include +#include + +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 00000000..6ac6e2e8 --- /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 + */ + +#ifndef __GDM_PASSWORD_EXTENSION_H +#define __GDM_PASSWORD_EXTENSION_H + +#include +#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 00000000..8fa5c7be --- /dev/null +++ b/gui/simple-greeter/extensions/password/page.ui @@ -0,0 +1,57 @@ + + + + + True + vertical + + + True + 6 + + + True + + + False + False + 0 + + + + + True + True + True + + + 1 + + + + + True + True + 0 + + + + + True + + + True + + + 0 + + + + + True + True + 1 + + + + diff --git a/gui/simple-greeter/extensions/smartcard/Makefile.am b/gui/simple-greeter/extensions/smartcard/Makefile.am new file mode 100644 index 00000000..d749cf9c --- /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 00000000..d5ac1fab --- /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 00000000..eb33f789 --- /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 + * + */ + +#include +#include "gdm-smartcard-extension.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 00000000..87f5b86d --- /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 + */ + +#ifndef __GDM_SMARTCARD_EXTENSION_H +#define __GDM_SMARTCARD_EXTENSION_H + +#include +#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 00000000..6ce58539 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 + +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 00000000..38e13c31 --- /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 +#include + +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 00000000..711c2c7f --- /dev/null +++ b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-worker.c @@ -0,0 +1,184 @@ +#include "config.h" + +#include +#include +#include +#include +#include + +#include + +#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'? "" : 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 00000000..77921467 --- /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 + * + * 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 +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +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 00000000..9f153cda --- /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 +#include + +#include + +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 00000000..661d6879 --- /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 00000000..0112af1b 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 00000000..e79d85bf --- /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 00000000..35d5578d 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 00000000..c20f10d0 --- /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 00000000..8fa5c7be --- /dev/null +++ b/gui/simple-greeter/extensions/smartcard/page.ui @@ -0,0 +1,57 @@ + + + + + True + vertical + + + True + 6 + + + True + + + False + False + 0 + + + + + True + True + True + + + 1 + + + + + True + True + 0 + + + + + True + + + True + + + 0 + + + + + True + True + 1 + + + + diff --git a/gui/simple-greeter/extensions/unified/Makefile.am b/gui/simple-greeter/extensions/unified/Makefile.am new file mode 100644 index 00000000..31fa7442 --- /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 00000000..a9f89b8e --- /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 + * + */ + +#include +#include "gdm-unified-extension.h" +#include "gdm-login-extension.h" + +#include +#include +#include + +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 00000000..d6b0eaa1 --- /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 + */ + +#ifndef __GDM_UNIFIED_EXTENSION_H +#define __GDM_UNIFIED_EXTENSION_H + +#include +#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 00000000..58c397d9 --- /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 00000000..8fa5c7be --- /dev/null +++ b/gui/simple-greeter/extensions/unified/page.ui @@ -0,0 +1,57 @@ + + + + + True + vertical + + + True + 6 + + + True + + + False + False + 0 + + + + + True + True + True + + + 1 + + + + + True + True + 0 + + + + + True + + + True + + + 0 + + + + + True + True + 1 + + + + 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 00000000..708b0e6c --- /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 + */ + +#include "config.h" +#include "gdm-cell-renderer-timer.h" +#include + +#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, + >k_widget_get_style (widget)->text_aa[widget_state], + >k_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 00000000..a3661d0e --- /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 + */ +#ifndef __GDM_CELL_RENDERER_TIMER_H +#define __GDM_CELL_RENDERER_TIMER_H + +#include + +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 00000000..cab506c5 --- /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 + * Copyright (C) 2007 William Jon McCann + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 ("%s\n" + "%s", + 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 00000000..d36e7eda --- /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 + * Copyright (C) 2007 William Jon McCann + * + * 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 +#include + +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 00000000..0c6fbde7 --- /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 + * 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 + * Ray Strode + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 ("%s", 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 00000000..065114eb --- /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 + * + * 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 + * Jon McCann + */ + +#ifndef __GDM_CLOCK_WIDGET_H +#define __GDM_CLOCK_WIDGET_H + +#include + +#include + +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 00000000..a0be786f --- /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 + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 00000000..0c9964ef --- /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 + */ + +#ifndef __GDM_EXTENSION_LIST_H +#define __GDM_EXTENSION_LIST_H + +#include +#include +#include + +#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 00000000..529c418a --- /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 + * 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 + * Ray Strode + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#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 +#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 ("%s", 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 00000000..6db3e046 --- /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 + * 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 +#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 00000000..163a8f47 --- /dev/null +++ b/gui/simple-greeter/gdm-greeter-login-window.ui @@ -0,0 +1,284 @@ + + + + + + True + 0 + out + + + True + 24 + + + True + 10 + + + True + + + True + 48 + computer + + + + + False + False + 0 + + + + + True + False + + + True + False + False + + + True + Computer Name + + + + + True + page 5 + + + False + + + + + True + Version + + + 1 + + + + + True + + + 1 + False + + + + + + + + True + + + 2 + False + + + + + + + + True + + + 3 + False + + + + + + + + True + + + 4 + False + + + + + + + + True + + + 5 + False + + + + + + + + True + + + 6 + False + + + + + + + False + False + 1 + + + + + True + center + True + + + 3 + + + + + True + + + True + 2 + + + True + 1.0 + 0.0 + + + False + + + + + False + False + 0 + + + + + False + + + True + True + 1 + + + + + True + 10 + + + + + + False + False + 2 + + + + + + + 4 + + + + + True + 6 + end + + + False + 0.0 + 0.0 + 0.0 + 1.0 + + + True + True + 0 + True + + + + + Cancel + True + True + + + False + False + 1 + + + + + Unlock + True + True + True + True + + + False + False + 2 + + + + + Login + True + True + True + False + False + + + False + False + 5 + + + + + True + True + end + 2 + + + + + + + + diff --git a/gui/simple-greeter/gdm-greeter-panel.c b/gui/simple-greeter/gdm-greeter-panel.c new file mode 100644 index 00000000..ce00b90c --- /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 + * 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 +#include +#include +#include +#include +#include + +#ifdef ENABLE_RBAC_SHUTDOWN +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_UPOWER +#include +#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 00000000..8724c2de --- /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 + * + * 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 +#include + +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 00000000..5e23fdba --- /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 + * + * 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 +#include +#include +#include + +#include +#include +#include + +#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 00000000..8036e09e --- /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 + * + * 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 + +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 00000000..ed0a1124 --- /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 + * + * 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 + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 \"*.*.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 ("%s%s%s", + is_default? "" : "", + name ? name : "", + is_default? "" : ""); + 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 00000000..63934d8c --- /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 + * + * 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_OPTION_WIDGET_H +#define __GDM_OPTION_WIDGET_H + +#include +#include + +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 00000000..45a00998 --- /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 + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#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 00000000..8e326587 --- /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 + * + * 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 + +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 00000000..66d6fb3f --- /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 + * + * Parts taken from gtkscrolledwindow.c in the GTK+ toolkit. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 00000000..57299246 --- /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 + */ + +#ifndef __GDM_SCROLLABLE_WIDGET_H +#define __GDM_SCROLLABLE_WIDGET_H + +#include + +#include + +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 00000000..f37da234 --- /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 + * 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 + * Ray Strode + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 00000000..aaeacc46 --- /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 + */ + +#ifndef __GDM_SESSION_OPTION_WIDGET_H +#define __GDM_SESSION_OPTION_WIDGET_H + +#include + +#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 00000000..c8e9db63 --- /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 + * + * 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 + * Ray Strode + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 00000000..217c67c5 --- /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 + * + * 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 + +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 00000000..9acd3f7a --- /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 + */ + +#include "config.h" +#include "gdm-timer.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 00000000..099eee5d --- /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 + */ + +#ifndef GDM_TIMER_H +#define GDM_TIMER_H + +#include + +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 00000000..639437f0 --- /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 + * + * 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 +#include +#include +#include + +#include +#include +#include +#include + +#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 00000000..dd203c5e --- /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 + * + * 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 +#include + +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 00000000..ccc07a3f --- /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 + * Copyright (C) 2007 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 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#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 00000000..d670ed72 --- /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 + * + * 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 + +#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 00000000..9bf3ca1f --- /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 . + * + * 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 + +#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 00000000..b17da46d --- /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 + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 00000000..07b55afa --- /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 00000000..fec049cf --- /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 + * + */ + +#include +#include + +#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 00000000..78836817 --- /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 +#include + +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 00000000..cf8c9aca --- /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 00000000..a167065c --- /dev/null +++ b/gui/simple-greeter/org.gnome.SessionManager.ClientPrivate.xml @@ -0,0 +1,123 @@ + + + + + + + + Whether or not it is OK to preceed + + + + + The reason string + + + + + This method should be used by the client in response to + the QueryEndSession + and EndSession signals. + + + + + + + + Stop client + + + The client should stop and remove itself from the session in + response to this signal. + + + + + + + + + Flags + + + + + This signal is used to inform the client that the + session is about to end. The client must respond by + calling + EndSessionResponse + within one second of the signal emission. + + + The flags may include: + + + 1 + Logout is forced. + EndSessionResponse + reason and any inhibit from client will be + ignored. + + + + + 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. + + + 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 + EndSession signal. + + + The client should limit operations until either a + EndSession + CancelEndSession + signal is received. + + + + + + + + + Flags + + + + + This signal is used to inform the client that the + session is about to end. The client must respond by + calling + EndSessionResponse + within ten seconds of the signal emission. + + + 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. + + + + + + + + + + This signal indicates to the client that a previous emission of + QueryEndSession + has been cancelled. The client should resume normal operations. + + + + + + + diff --git a/gui/simple-greeter/org.gnome.SessionManager.xml b/gui/simple-greeter/org.gnome.SessionManager.xml new file mode 100644 index 00000000..eaf1ef53 --- /dev/null +++ b/gui/simple-greeter/org.gnome.SessionManager.xml @@ -0,0 +1,392 @@ + + + + + + + + + + + The variable name + + + + + The value + + + + + Adds the variable name to the application launch environment with the specified value. May only be used during the Session Manager initialization phase. + + + + + + + + The error message + + + + + Whether the error should be treated as fatal + + + + + May be used by applications launched during the Session Manager initialization phase to indicate there was a problem. + + + + + + + + + + + The application identifier + + + + + Client startup identifier + + + + + The object path of the newly registered client + + + + + Register the caller as a Session Management client. + + + + + + + + + The object path of the client + + + + + Unregister the specified client from Session Management. + + + + + + + + + The application identifier + + + + + The toplevel X window identifier + + + + + The reason for the inhibit + + + + + Flags that spefify what should be inhibited + + + + + The cookie + + + + + 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. + + + 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 Uninhibit() + or disconnect from the session bus. + + + 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. + + + Reasons should be short and to the point. + + + The flags parameter must include at least one of the following: + + + 1 + Inhibit logging out + + + 2 + Inhibit user switching + + + 4 + Inhibit suspending the session or computer + + + 8 + Inhibit the session being marked as idle + + + Values for flags may be bitwise or'ed together. + + + The returned cookie is used to uniquely identify this request. It should be used + as an argument to Uninhibit() in + order to remove the request. + + + + + + + + + + The cookie + + + + + Cancel a previous call to Inhibit() identified by the cookie. + + + + + + + + Flags that spefify what should be inhibited + + + + + Returns TRUE if any of the operations in the bitfield flags are inhibited + + + + + Determine if operation(s) specified by the flags + are currently inhibited. Flags are same as those accepted + by the + Inhibit() + method. + + + + + + + + an array of client IDs + + + + + This gets a list of all the Clients + that are currently known to the session manager. + Each Client ID is an D-Bus object path for the object that implements the + Client interface. + + org.gnome.SessionManager.Client + + + + + + + an array of inhibitor IDs + + + + + This gets a list of all the Inhibitors + that are currently known to the session manager. + Each Inhibitor ID is an D-Bus object path for the object that implements the + Inhibitor interface. + + org.gnome.SessionManager.Inhibitor + + + + + + + + The autostart condition string + + + + + True if condition is handled, false otherwise + + + + + Allows the caller to determine whether the session manager is + handling changes to the specified autostart condition. + + + + + + + + Request a shutdown dialog + + + + + + + + True if shutdown is available to the user, false otherwise + + + + + Allows the caller to determine whether or not it's okay to show + a shutdown option in the UI + + + + + + + + The type of logout that is being requested + + + + + Request a logout dialog + + Allowed values for the mode parameter are: + + + 0 + Normal. + + + 1 + No confirmation inferface should be shown. + + + 2 + Forcefully logout. No confirmation will be shown and any inhibitors will be ignored. + + + Values for flags may be bitwise or'ed together. + + + + + + + + + True if the session has entered the Running phase, false otherwise + + + + + Allows the caller to determine whether the session manager + has entered the Running phase, in case the client missed the + SessionRunning signal. + + + + + + + + + + The object path for the added client + + + + + Emitted when a client has been added to the session manager. + + + + + + + + The object path for the removed client + + + + + Emitted when a client has been removed from the session manager. + + + + + + + + + The object path for the added inhibitor + + + + + Emitted when an inhibitor has been added to the session manager. + + + + + + + + The object path for the removed inhibitor + + + + + Emitted when an inhibitor has been removed from the session manager. + + + + + + + + + Indicates the session has entered the Running phase. + + + + + + + + Indicates the session is about to end. + + + + + + diff --git a/gui/simple-greeter/test-a11y-preferences.c b/gui/simple-greeter/test-a11y-preferences.c new file mode 100644 index 00000000..dfe29608 --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 00000000..30b795fb --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +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 00000000..03db0a21 --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 00000000..dda61374 --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 00000000..bac37b72 --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 00000000..e33d608a --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 00000000..d0096a9c --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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[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 00000000..71de1b09 --- /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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 00000000..727163f5 --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 00000000..71d75141 --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#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; +} -- cgit v1.2.1