diff options
Diffstat (limited to 'gui')
85 files changed, 21894 insertions, 0 deletions
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 <rstrode@redhat.com> + * + */ + +#include <config.h> +#include <stdlib.h> + +#include "gdm-fingerprint-extension.h" + +#include <glib/gi18n-lib.h> +#include <gio/gio.h> +#include <gtk/gtk.h> + +struct _GdmFingerprintExtensionPrivate +{ + GIcon *icon; + GtkWidget *page; + GtkActionGroup *actions; + GSettings *settings; + + GtkWidget *message_label; + GtkWidget *prompt_label; + GtkWidget *prompt_entry; + + GQueue *message_queue; + guint message_timeout_id; + + GDBusConnection *bus_connection; + + guint answer_pending : 1; +}; + +typedef struct { + char *text; + GdmServiceMessageType type; +} QueuedMessage; + +static void gdm_fingerprint_extension_finalize (GObject *object); + +static void gdm_login_extension_iface_init (GdmLoginExtensionIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GdmFingerprintExtension, + gdm_fingerprint_extension, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GDM_TYPE_LOGIN_EXTENSION, + gdm_login_extension_iface_init)); + +static void +set_message (GdmFingerprintExtension *extension, + const char *message) +{ + gtk_widget_show (extension->priv->message_label); + gtk_label_set_text (GTK_LABEL (extension->priv->message_label), message); +} + +static void +free_queued_message (QueuedMessage *message) +{ + g_free (message->text); + g_slice_free (QueuedMessage, message); +} + +static void +purge_message_queue (GdmFingerprintExtension *extension) +{ + if (extension->priv->message_timeout_id) { + g_source_remove (extension->priv->message_timeout_id); + extension->priv->message_timeout_id = 0; + } + g_queue_foreach (extension->priv->message_queue, + (GFunc) free_queued_message, + NULL); + g_queue_clear (extension->priv->message_queue); +} + +static gboolean +dequeue_message (GdmFingerprintExtension *extension) +{ + if (!g_queue_is_empty (extension->priv->message_queue)) { + int duration; + gboolean needs_beep; + + QueuedMessage *message; + message = (QueuedMessage *) g_queue_pop_head (extension->priv->message_queue); + + switch (message->type) { + case GDM_SERVICE_MESSAGE_TYPE_INFO: + needs_beep = FALSE; + break; + case GDM_SERVICE_MESSAGE_TYPE_PROBLEM: + needs_beep = TRUE; + break; + default: + g_assert_not_reached (); + } + + set_message (extension, message->text); + + duration = (int) (g_utf8_strlen (message->text, -1) / 66.0) * 1000; + duration = CLAMP (duration, 400, 3000); + + extension->priv->message_timeout_id = g_timeout_add (duration, + (GSourceFunc) dequeue_message, + extension); + if (needs_beep) { + gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (extension))); + } + + free_queued_message (message); + } else { + extension->priv->message_timeout_id = 0; + + _gdm_login_extension_emit_message_queue_empty (GDM_LOGIN_EXTENSION (extension)); + } + + return FALSE; +} + +static void +gdm_fingerprint_extension_queue_message (GdmLoginExtension *login_extension, + GdmServiceMessageType type, + const char *text) +{ + GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension); + + QueuedMessage *message = g_slice_new (QueuedMessage); + + message->text = g_strdup (text); + message->type = type; + + g_queue_push_tail (extension->priv->message_queue, message); + + if (extension->priv->message_timeout_id == 0) { + dequeue_message (extension); + } +} + +static void +gdm_fingerprint_extension_ask_question (GdmLoginExtension *login_extension, + const char *message) +{ + GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension); + gtk_widget_show (extension->priv->prompt_label); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); + gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE); + gtk_widget_show (extension->priv->prompt_entry); + gtk_widget_grab_focus (extension->priv->prompt_entry); + extension->priv->answer_pending = TRUE; +} + +static void +gdm_fingerprint_extension_ask_secret (GdmLoginExtension *login_extension, + const char *message) +{ + GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension); + gtk_widget_show (extension->priv->prompt_label); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message); + gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), FALSE); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); + gtk_widget_show (extension->priv->prompt_entry); + gtk_widget_grab_focus (extension->priv->prompt_entry); + extension->priv->answer_pending = TRUE; +} + +static void +gdm_fingerprint_extension_reset (GdmLoginExtension *login_extension) +{ + GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension); + gtk_widget_hide (extension->priv->prompt_label); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), ""); + + gtk_widget_hide (extension->priv->prompt_entry); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); + gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE); + extension->priv->answer_pending = FALSE; + + set_message (extension, ""); + purge_message_queue (extension); + + gdm_login_extension_set_enabled (login_extension, FALSE); +} + +static void +gdm_fingerprint_extension_set_ready (GdmLoginExtension *login_extension) +{ + gdm_login_extension_set_enabled (login_extension, TRUE); +} + +static char * +gdm_fingerprint_extension_get_service_name (GdmLoginExtension *login_extension) +{ + return g_strdup (GDM_FINGERPRINT_EXTENSION_SERVICE_NAME); +} + +static GtkWidget * +gdm_fingerprint_extension_get_page (GdmLoginExtension *login_extension) +{ + GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension); + return extension->priv->page; +} + +static GtkActionGroup * +gdm_fingerprint_extension_get_actions (GdmLoginExtension *login_extension) +{ + GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension); + + return g_object_ref (extension->priv->actions); +} + +static void +gdm_fingerprint_extension_request_answer (GdmLoginExtension *login_extension) +{ + GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension); + const char *text; + + if (!extension->priv->answer_pending) { + _gdm_login_extension_emit_answer (login_extension, NULL); + return; + } + + extension->priv->answer_pending = FALSE; + text = gtk_entry_get_text (GTK_ENTRY (extension->priv->prompt_entry)); + _gdm_login_extension_emit_answer (login_extension, text); + + gtk_widget_hide (extension->priv->prompt_entry); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), ""); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); +} + +static gboolean +gdm_fingerprint_extension_focus (GdmLoginExtension *login_extension) +{ + GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension); + + if (!extension->priv->answer_pending) { + return FALSE; + } + + gtk_widget_grab_focus (extension->priv->prompt_entry); + return TRUE; +} + +static gboolean +gdm_fingerprint_extension_has_queued_messages (GdmLoginExtension *login_extension) +{ + GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension); + + if (extension->priv->message_timeout_id != 0) { + return TRUE; + } + + if (!g_queue_is_empty (extension->priv->message_queue)) { + return TRUE; + } + + return FALSE; +} + +static GIcon * +gdm_fingerprint_extension_get_icon (GdmLoginExtension *login_extension) +{ + GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension); + return g_object_ref (extension->priv->icon); +} + +static char * +gdm_fingerprint_extension_get_name (GdmLoginExtension *extension) +{ + return g_strdup (_("Fingerprint Authentication")); +} + +static char * +gdm_fingerprint_extension_get_description (GdmLoginExtension *extension) +{ + return g_strdup (_("Log into session with fingerprint")); +} + +static gboolean +gdm_fingerprint_extension_is_choosable (GdmLoginExtension *extension) +{ + return FALSE; +} + +static gboolean +gdm_fingerprint_extension_is_visible (GdmLoginExtension *login_extension) +{ + GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (login_extension); + GVariant *device_variant; + char *contents, **lines; + int i; + + if (!g_settings_get_boolean (extension->priv->settings, "enable-fingerprint-authentication")) { + return FALSE; + } + + if (extension->priv->bus_connection == NULL) { + return FALSE; + } + + device_variant = + g_dbus_connection_call_sync (extension->priv->bus_connection, + "net.reactivated.Fprint", + "/net/reactivated/Fprint/Manager", + "net.reactivated.Fprint.Manager", + "GetDefaultDevice", + NULL, G_VARIANT_TYPE_OBJECT_PATH, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL); + if (device_variant == NULL) { + return FALSE; + } + + g_variant_unref (device_variant); + + return TRUE; +} + +static void +gdm_login_extension_iface_init (GdmLoginExtensionIface *iface) +{ + iface->get_icon = gdm_fingerprint_extension_get_icon; + iface->get_description = gdm_fingerprint_extension_get_description; + iface->get_name = gdm_fingerprint_extension_get_name; + iface->is_choosable = gdm_fingerprint_extension_is_choosable; + iface->is_visible = gdm_fingerprint_extension_is_visible; + iface->queue_message = gdm_fingerprint_extension_queue_message; + iface->ask_question = gdm_fingerprint_extension_ask_question; + iface->ask_secret = gdm_fingerprint_extension_ask_secret; + iface->reset = gdm_fingerprint_extension_reset; + iface->set_ready = gdm_fingerprint_extension_set_ready; + iface->get_service_name = gdm_fingerprint_extension_get_service_name; + iface->get_page = gdm_fingerprint_extension_get_page; + iface->get_actions = gdm_fingerprint_extension_get_actions; + iface->request_answer = gdm_fingerprint_extension_request_answer; + iface->focus = gdm_fingerprint_extension_focus; + iface->has_queued_messages = gdm_fingerprint_extension_has_queued_messages; +} + +static void +gdm_fingerprint_extension_class_init (GdmFingerprintExtensionClass *extension_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (extension_class); + + object_class->finalize = gdm_fingerprint_extension_finalize; + + g_type_class_add_private (extension_class, + sizeof (GdmFingerprintExtensionPrivate)); +} + +static void +gdm_fingerprint_extension_finalize (GObject *object) +{ + GdmFingerprintExtension *extension = GDM_FINGERPRINT_EXTENSION (object); + + purge_message_queue (extension); + + if (extension->priv->bus_connection != NULL) { + g_object_unref (extension->priv->bus_connection); + } +} + +static void +create_page (GdmFingerprintExtension *extension) +{ + GtkBuilder *builder; + GObject *object; + GError *error; + + builder = gtk_builder_new (); + + error = NULL; + gtk_builder_add_from_file (builder, + PLUGINDATADIR "/page.ui", + &error); + + if (error != NULL) { + g_warning ("Could not load UI file: %s", error->message); + g_error_free (error); + return; + } + + object = gtk_builder_get_object (builder, "page"); + g_object_ref (object); + + extension->priv->page = GTK_WIDGET (object); + + object = gtk_builder_get_object (builder, "auth-prompt-label"); + g_object_ref (object); + extension->priv->prompt_label = GTK_WIDGET (object); + gtk_widget_hide (extension->priv->prompt_label); + + object = gtk_builder_get_object (builder, "auth-prompt-entry"); + g_object_ref (object); + extension->priv->prompt_entry = GTK_WIDGET (object); + gtk_widget_hide (extension->priv->prompt_entry); + + object = gtk_builder_get_object (builder, "auth-message-label"); + g_object_ref (object); + extension->priv->message_label = GTK_WIDGET (object); + gtk_widget_show (extension->priv->message_label); + + g_object_unref (builder); +} + +static void +create_actions (GdmFingerprintExtension *extension) +{ + extension->priv->actions = gtk_action_group_new (GDM_FINGERPRINT_EXTENSION_NAME); +} + +static void +gdm_fingerprint_extension_init (GdmFingerprintExtension *extension) +{ + extension->priv = G_TYPE_INSTANCE_GET_PRIVATE (extension, + GDM_TYPE_FINGERPRINT_EXTENSION, + GdmFingerprintExtensionPrivate); + + extension->priv->icon = g_themed_icon_new ("gdm-fingerprint"); + create_page (extension); + create_actions (extension); + + extension->priv->message_queue = g_queue_new (); + + extension->priv->settings = g_settings_new ("org.gnome.login-screen"); + extension->priv->bus_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL); + + gdm_fingerprint_extension_reset (GDM_LOGIN_EXTENSION (extension)); +} + +void +g_io_module_load (GIOModule *module) +{ + g_io_extension_point_implement (GDM_LOGIN_EXTENSION_POINT_NAME, + GDM_TYPE_FINGERPRINT_EXTENSION, + GDM_FINGERPRINT_EXTENSION_NAME, + 0); +} + +void +g_io_module_unload (GIOModule *module) +{ +} diff --git a/gui/simple-greeter/extensions/fingerprint/gdm-fingerprint-extension.h b/gui/simple-greeter/extensions/fingerprint/gdm-fingerprint-extension.h new file mode 100644 index 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 <rstrode@redhat.com> + */ + +#ifndef __GDM_FINGERPRINT_EXTENSION_H +#define __GDM_FINGERPRINT_EXTENSION_H + +#include <glib-object.h> +#include "gdm-login-extension.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_FINGERPRINT_EXTENSION (gdm_fingerprint_extension_get_type ()) +#define GDM_FINGERPRINT_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_FINGERPRINT_EXTENSION, GdmFingerprintExtension)) +#define GDM_FINGERPRINT_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_FINGERPRINT_EXTENSION, GdmFingerprintExtensionClass)) +#define GDM_IS_FINGERPRINT_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDM_TYPE_FINGERPRINT_EXTENSION)) +#define GDM_IS_FINGERPRINT_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDM_TYPE_FINGERPRINT_EXTENSION)) +#define GDM_FINGERPRINT_EXTENSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GDM_TYPE_FINGERPRINT_EXTENSION, GdmFingerprintExtensionClass)) + +#define GDM_FINGERPRINT_EXTENSION_NAME "gdm-fingerprint-extension" + +typedef struct _GdmFingerprintExtensionPrivate GdmFingerprintExtensionPrivate; + +typedef struct +{ + GObject parent; + GdmFingerprintExtensionPrivate *priv; +} GdmFingerprintExtension; + +typedef struct +{ + GObjectClass parent_class; +} GdmFingerprintExtensionClass; + +GType gdm_fingerprint_extension_get_type (void); + +G_END_DECLS + +#endif /* GDM_FINGERPRINT_EXTENSION_H */ diff --git a/gui/simple-greeter/extensions/fingerprint/icons/16x16/Makefile.am b/gui/simple-greeter/extensions/fingerprint/icons/16x16/Makefile.am new file mode 100644 index 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 Binary files differnew file mode 100644 index 00000000..4438cee2 --- /dev/null +++ b/gui/simple-greeter/extensions/fingerprint/icons/16x16/gdm-fingerprint.png 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 Binary files differnew file mode 100644 index 00000000..fd6f546c --- /dev/null +++ b/gui/simple-greeter/extensions/fingerprint/icons/48x48/gdm-fingerprint.png 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 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.14"/> + <object class="GtkVBox" id="page"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkHBox" id="auth-input-box"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="auth-prompt-label"> + <property name="visible">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="auth-prompt-entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="activates_default">True</property> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="auth-message-box"> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="auth-message-label"> + <property name="visible">True</property> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> +</interface> diff --git a/gui/simple-greeter/extensions/password/Makefile.am b/gui/simple-greeter/extensions/password/Makefile.am new file mode 100644 index 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 <rstrode@redhat.com> + * + */ + +#include <config.h> +#include "gdm-password-extension.h" +#include "gdm-login-extension.h" + +#include <glib/gi18n-lib.h> +#include <gio/gio.h> +#include <gtk/gtk.h> + +struct _GdmPasswordExtensionPrivate +{ + GIcon *icon; + GtkWidget *page; + GtkActionGroup *actions; + GtkAction *login_action; + + GtkWidget *message_label; + GtkWidget *prompt_label; + GtkWidget *prompt_entry; + + GQueue *message_queue; + guint message_timeout_id; + + guint answer_pending : 1; +}; + +typedef struct { + char *text; + GdmServiceMessageType type; +} QueuedMessage; + +static void gdm_password_extension_finalize (GObject *object); + +static void gdm_login_extension_iface_init (GdmLoginExtensionIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GdmPasswordExtension, + gdm_password_extension, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GDM_TYPE_LOGIN_EXTENSION, + gdm_login_extension_iface_init)); + +static void +set_message (GdmPasswordExtension *extension, + const char *message) +{ + gtk_widget_show (extension->priv->message_label); + gtk_label_set_text (GTK_LABEL (extension->priv->message_label), message); +} + +static void +free_queued_message (QueuedMessage *message) +{ + g_free (message->text); + g_slice_free (QueuedMessage, message); +} + +static void +purge_message_queue (GdmPasswordExtension *extension) +{ + if (extension->priv->message_timeout_id) { + g_source_remove (extension->priv->message_timeout_id); + extension->priv->message_timeout_id = 0; + } + g_queue_foreach (extension->priv->message_queue, + (GFunc) free_queued_message, + NULL); + g_queue_clear (extension->priv->message_queue); +} + +static gboolean +dequeue_message (GdmPasswordExtension *extension) +{ + if (!g_queue_is_empty (extension->priv->message_queue)) { + int duration; + gboolean needs_beep; + + QueuedMessage *message; + message = (QueuedMessage *) g_queue_pop_head (extension->priv->message_queue); + + switch (message->type) { + case GDM_SERVICE_MESSAGE_TYPE_INFO: + needs_beep = FALSE; + break; + case GDM_SERVICE_MESSAGE_TYPE_PROBLEM: + needs_beep = TRUE; + break; + default: + g_assert_not_reached (); + } + + set_message (extension, message->text); + + duration = (int) (g_utf8_strlen (message->text, -1) / 66.0) * 1000; + duration = CLAMP (duration, 400, 3000); + + extension->priv->message_timeout_id = g_timeout_add (duration, + (GSourceFunc) dequeue_message, + extension); + if (needs_beep) { + gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (extension->priv->page))); + } + + free_queued_message (message); + } else { + extension->priv->message_timeout_id = 0; + + _gdm_login_extension_emit_message_queue_empty (GDM_LOGIN_EXTENSION (extension)); + } + + return FALSE; +} + +static void +gdm_password_extension_queue_message (GdmLoginExtension *login_extension, + GdmServiceMessageType type, + const char *text) +{ + GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension); + + QueuedMessage *message = g_slice_new (QueuedMessage); + + message->text = g_strdup (text); + message->type = type; + + g_queue_push_tail (extension->priv->message_queue, message); + + if (extension->priv->message_timeout_id == 0) { + dequeue_message (extension); + } +} + +static void +gdm_password_extension_ask_question (GdmLoginExtension *login_extension, + const char *message) +{ + GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension); + gtk_widget_show (extension->priv->prompt_label); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); + gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE); + gtk_widget_show (extension->priv->prompt_entry); + gtk_widget_grab_focus (extension->priv->prompt_entry); + extension->priv->answer_pending = TRUE; + + gtk_action_set_sensitive (extension->priv->login_action, TRUE); +} + +static void +gdm_password_extension_ask_secret (GdmLoginExtension *login_extension, + const char *message) +{ + GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension); + gtk_widget_show (extension->priv->prompt_label); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message); + gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), FALSE); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); + gtk_widget_show (extension->priv->prompt_entry); + gtk_widget_grab_focus (extension->priv->prompt_entry); + extension->priv->answer_pending = TRUE; + + gtk_action_set_sensitive (extension->priv->login_action, TRUE); +} + +static void +gdm_password_extension_reset (GdmLoginExtension *login_extension) +{ + GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension); + gtk_widget_hide (extension->priv->prompt_label); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), ""); + + gtk_widget_hide (extension->priv->prompt_entry); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); + gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE); + extension->priv->answer_pending = FALSE; + + set_message (extension, ""); + purge_message_queue (extension); + + gdm_login_extension_set_enabled (login_extension, FALSE); +} + +static void +gdm_password_extension_set_ready (GdmLoginExtension *extension) +{ + gdm_login_extension_set_enabled (extension, TRUE); +} + +static char * +gdm_password_extension_get_service_name (GdmLoginExtension *extension) +{ + return g_strdup (GDM_PASSWORD_EXTENSION_SERVICE_NAME); +} + +static GtkWidget * +gdm_password_extension_get_page (GdmLoginExtension *login_extension) +{ + GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension); + return extension->priv->page; +} + +static GtkActionGroup * +gdm_password_extension_get_actions (GdmLoginExtension *login_extension) +{ + GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension); + return g_object_ref (extension->priv->actions); +} + +static void +request_answer (GdmPasswordExtension *extension) +{ + const char *text; + + if (!extension->priv->answer_pending) { + _gdm_login_extension_emit_answer (GDM_LOGIN_EXTENSION (extension), NULL); + return; + } + + extension->priv->answer_pending = FALSE; + text = gtk_entry_get_text (GTK_ENTRY (extension->priv->prompt_entry)); + _gdm_login_extension_emit_answer (GDM_LOGIN_EXTENSION (extension), text); + + gtk_widget_hide (extension->priv->prompt_entry); + gtk_widget_hide (extension->priv->prompt_label); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), ""); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); +} + +static gboolean +gdm_password_extension_focus (GdmLoginExtension *login_extension) +{ + GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension); + if (!extension->priv->answer_pending) { + _gdm_login_extension_emit_answer (login_extension, NULL); + return FALSE; + } + + gtk_widget_grab_focus (extension->priv->prompt_entry); + return TRUE; +} + +static gboolean +gdm_password_extension_has_queued_messages (GdmLoginExtension *login_extension) +{ + GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension); + + if (extension->priv->message_timeout_id != 0) { + return TRUE; + } + + if (!g_queue_is_empty (extension->priv->message_queue)) { + return TRUE; + } + + return FALSE; +} + +static GIcon * +gdm_password_extension_get_icon (GdmLoginExtension *login_extension) +{ + GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (login_extension); + return g_object_ref (extension->priv->icon); +} + +static char * +gdm_password_extension_get_name (GdmLoginExtension *login_extension) +{ + return g_strdup (_("Password Authentication")); +} + +static char * +gdm_password_extension_get_description (GdmLoginExtension *login_extension) +{ + return g_strdup (_("Log into session with username and password")); +} + +static gboolean +gdm_password_extension_is_choosable (GdmLoginExtension *login_extension) +{ + return FALSE; +} + +static gboolean +gdm_password_extension_is_visible (GdmLoginExtension *login_extension) +{ + return TRUE; +} + +static void +gdm_login_extension_iface_init (GdmLoginExtensionIface *iface) +{ + iface->get_icon = gdm_password_extension_get_icon; + iface->get_description = gdm_password_extension_get_description; + iface->get_name = gdm_password_extension_get_name; + iface->is_choosable = gdm_password_extension_is_choosable; + iface->is_visible = gdm_password_extension_is_visible; + iface->queue_message = gdm_password_extension_queue_message; + iface->ask_question = gdm_password_extension_ask_question; + iface->ask_secret = gdm_password_extension_ask_secret; + iface->reset = gdm_password_extension_reset; + iface->set_ready = gdm_password_extension_set_ready; + iface->get_service_name = gdm_password_extension_get_service_name; + iface->get_page = gdm_password_extension_get_page; + iface->get_actions = gdm_password_extension_get_actions; + iface->focus = gdm_password_extension_focus; + iface->has_queued_messages = gdm_password_extension_has_queued_messages; +} + +static void +gdm_password_extension_class_init (GdmPasswordExtensionClass *extension_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (extension_class); + + object_class->finalize = gdm_password_extension_finalize; + + g_type_class_add_private (extension_class, + sizeof (GdmPasswordExtensionPrivate)); +} + +static void +gdm_password_extension_finalize (GObject *object) +{ + GdmPasswordExtension *extension = GDM_PASSWORD_EXTENSION (object); + + purge_message_queue (extension); +} + +static void +on_activate_log_in (GdmPasswordExtension *extension, + GtkAction *action) +{ + request_answer (extension); + gtk_action_set_sensitive (action, FALSE); +} + +static void +create_page (GdmPasswordExtension *extension) +{ + GtkBuilder *builder; + GObject *object; + GError *error; + + builder = gtk_builder_new (); + + error = NULL; + gtk_builder_add_from_file (builder, + PLUGINDATADIR "/page.ui", + &error); + + if (error != NULL) { + g_warning ("Could not load UI file: %s", error->message); + g_error_free (error); + return; + } + + object = gtk_builder_get_object (builder, "page"); + g_object_ref (object); + + extension->priv->page = GTK_WIDGET (object); + + object = gtk_builder_get_object (builder, "auth-prompt-label"); + g_object_ref (object); + extension->priv->prompt_label = GTK_WIDGET (object); + gtk_widget_hide (extension->priv->prompt_label); + + object = gtk_builder_get_object (builder, "auth-prompt-entry"); + g_object_ref (object); + extension->priv->prompt_entry = GTK_WIDGET (object); + gtk_widget_hide (extension->priv->prompt_entry); + + object = gtk_builder_get_object (builder, "auth-message-label"); + g_object_ref (object); + extension->priv->message_label = GTK_WIDGET (object); + gtk_widget_show (extension->priv->message_label); + + g_object_unref (builder); +} + +static void +create_actions (GdmPasswordExtension *extension) +{ + GtkAction *action; + + extension->priv->actions = gtk_action_group_new (GDM_PASSWORD_EXTENSION_NAME); + + action = gtk_action_new (GDM_LOGIN_EXTENSION_DEFAULT_ACTION, + _("Log In"), NULL, NULL); + g_signal_connect_swapped (action, "activate", + G_CALLBACK (on_activate_log_in), extension); + g_object_set (G_OBJECT (action), "icon-name", "go-home", NULL); + gtk_action_group_add_action (extension->priv->actions, + action); + + extension->priv->login_action = action; +} + +static void +gdm_password_extension_init (GdmPasswordExtension *extension) +{ + extension->priv = G_TYPE_INSTANCE_GET_PRIVATE (extension, + GDM_TYPE_PASSWORD_EXTENSION, + GdmPasswordExtensionPrivate); + + extension->priv->icon = g_themed_icon_new ("dialog-password"); + create_page (extension); + create_actions (extension); + + extension->priv->message_queue = g_queue_new (); + + gdm_password_extension_reset (GDM_LOGIN_EXTENSION (extension)); +} + +void +g_io_module_load (GIOModule *module) +{ + g_io_extension_point_implement (GDM_LOGIN_EXTENSION_POINT_NAME, + GDM_TYPE_PASSWORD_EXTENSION, + GDM_PASSWORD_EXTENSION_NAME, + G_MAXINT); +} + +void +g_io_module_unload (GIOModule *module) +{ +} diff --git a/gui/simple-greeter/extensions/password/gdm-password-extension.h b/gui/simple-greeter/extensions/password/gdm-password-extension.h new file mode 100644 index 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 <rstrode@redhat.com> + */ + +#ifndef __GDM_PASSWORD_EXTENSION_H +#define __GDM_PASSWORD_EXTENSION_H + +#include <glib-object.h> +#include "gdm-login-extension.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_PASSWORD_EXTENSION (gdm_password_extension_get_type ()) +#define GDM_PASSWORD_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_PASSWORD_EXTENSION, GdmPasswordExtension)) +#define GDM_PASSWORD_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_PASSWORD_EXTENSION, GdmPasswordExtensionClass)) +#define GDM_IS_PASSWORD_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDM_TYPE_PASSWORD_EXTENSION)) +#define GDM_IS_PASSWORD_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDM_TYPE_PASSWORD_EXTENSION)) +#define GDM_PASSWORD_EXTENSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GDM_TYPE_PASSWORD_EXTENSION, GdmPasswordExtensionClass)) + +#define GDM_PASSWORD_EXTENSION_NAME "gdm-password-extension" + +typedef struct _GdmPasswordExtensionPrivate GdmPasswordExtensionPrivate; + +typedef struct +{ + GObject parent; + GdmPasswordExtensionPrivate *priv; +} GdmPasswordExtension; + +typedef struct +{ + GObjectClass parent_class; +} GdmPasswordExtensionClass; + +GType gdm_password_extension_get_type (void); + +G_END_DECLS + +#endif /* GDM_PASSWORD_EXTENSION_H */ diff --git a/gui/simple-greeter/extensions/password/page.ui b/gui/simple-greeter/extensions/password/page.ui new file mode 100644 index 00000000..8fa5c7be --- /dev/null +++ b/gui/simple-greeter/extensions/password/page.ui @@ -0,0 +1,57 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.14"/> + <object class="GtkVBox" id="page"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkHBox" id="auth-input-box"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="auth-prompt-label"> + <property name="visible">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="auth-prompt-entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="activates_default">True</property> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="auth-message-box"> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="auth-message-label"> + <property name="visible">True</property> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> +</interface> diff --git a/gui/simple-greeter/extensions/smartcard/Makefile.am b/gui/simple-greeter/extensions/smartcard/Makefile.am new file mode 100644 index 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 <rstrode@redhat.com> + * + */ + +#include <config.h> +#include "gdm-smartcard-extension.h" + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> + +#include <glib/gi18n-lib.h> +#include <gio/gio.h> +#include <gtk/gtk.h> + +#ifndef GDM_SMARTCARD_WORKER_COMMAND +#define GDM_SMARTCARD_WORKER_COMMAND LIBEXECDIR "/gdm-smartcard-worker" +#endif + +struct _GdmSmartcardExtensionPrivate +{ + GIcon *icon; + GtkWidget *page; + GtkActionGroup *actions; + GtkAction *login_action; + GSettings *settings; + + GtkWidget *message_label; + GtkWidget *prompt_label; + GtkWidget *prompt_entry; + + GPid worker_pid; + int number_of_tokens; + + GQueue *message_queue; + guint message_timeout_id; + + guint answer_pending : 1; + guint select_when_ready : 1; +}; + +typedef struct { + char *text; + GdmServiceMessageType type; +} QueuedMessage; + +static void gdm_smartcard_extension_finalize (GObject *object); + +static void gdm_login_extension_iface_init (GdmLoginExtensionIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GdmSmartcardExtension, + gdm_smartcard_extension, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GDM_TYPE_LOGIN_EXTENSION, + gdm_login_extension_iface_init)); + +static void +set_message (GdmSmartcardExtension *extension, + const char *message) +{ + gtk_widget_show (extension->priv->message_label); + gtk_label_set_text (GTK_LABEL (extension->priv->message_label), message); +} + +static void +free_queued_message (QueuedMessage *message) +{ + g_free (message->text); + g_slice_free (QueuedMessage, message); +} + +static void +purge_message_queue (GdmSmartcardExtension *extension) +{ + if (extension->priv->message_timeout_id) { + g_source_remove (extension->priv->message_timeout_id); + extension->priv->message_timeout_id = 0; + } + g_queue_foreach (extension->priv->message_queue, + (GFunc) free_queued_message, + NULL); + g_queue_clear (extension->priv->message_queue); +} + +static gboolean +dequeue_message (GdmSmartcardExtension *extension) +{ + if (!g_queue_is_empty (extension->priv->message_queue)) { + int duration; + gboolean needs_beep; + + QueuedMessage *message; + message = (QueuedMessage *) g_queue_pop_head (extension->priv->message_queue); + + switch (message->type) { + case GDM_SERVICE_MESSAGE_TYPE_INFO: + needs_beep = FALSE; + break; + case GDM_SERVICE_MESSAGE_TYPE_PROBLEM: + needs_beep = TRUE; + break; + default: + g_assert_not_reached (); + } + + set_message (extension, message->text); + + duration = (int) (g_utf8_strlen (message->text, -1) / 66.0) * 1000; + duration = CLAMP (duration, 400, 3000); + + extension->priv->message_timeout_id = g_timeout_add (duration, + (GSourceFunc) dequeue_message, + extension); + if (needs_beep) { + gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (extension))); + } + + free_queued_message (message); + } else { + extension->priv->message_timeout_id = 0; + + _gdm_login_extension_emit_message_queue_empty (GDM_LOGIN_EXTENSION (extension)); + } + + return FALSE; +} + +static void +gdm_smartcard_extension_queue_message (GdmLoginExtension *login_extension, + GdmServiceMessageType type, + const char *text) +{ + GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension); + + QueuedMessage *message = g_slice_new (QueuedMessage); + + message->text = g_strdup (text); + message->type = type; + + g_queue_push_tail (extension->priv->message_queue, message); + + if (extension->priv->message_timeout_id == 0) { + dequeue_message (extension); + } +} + +static gboolean +on_smartcard_event (GIOChannel *io_channel, + GIOCondition condition, + gpointer data) +{ + GdmSmartcardExtension *extension; + + extension = GDM_SMARTCARD_EXTENSION (data); + + if (condition & G_IO_IN) { + char buffer[1024]; + ssize_t num_bytes; + + num_bytes = read (g_io_channel_unix_get_fd (io_channel), + buffer, sizeof (buffer)); + + if (num_bytes < 0 && errno != EINTR) + return FALSE; + + if (num_bytes != 1) { + g_debug ("buffer: %s\n", buffer); + return TRUE; + } + + if (buffer[0] == 'I') { + extension->priv->number_of_tokens++; + } else { + extension->priv->number_of_tokens--; + } + + if (extension->priv->number_of_tokens == 1) { + if (!_gdm_login_extension_emit_choose_user (GDM_LOGIN_EXTENSION (extension), + GDM_SMARTCARD_EXTENSION_SERVICE_NAME)) { + g_debug ("could not choose smart card user, cancelling..."); + _gdm_login_extension_emit_cancel (GDM_LOGIN_EXTENSION (extension)); + extension->priv->select_when_ready = TRUE; + } else { + g_debug ("chose smart card user!"); + } + } else if (extension->priv->number_of_tokens == 0) { + _gdm_login_extension_emit_cancel (GDM_LOGIN_EXTENSION (extension)); + } + + return TRUE; + } + + if (condition & G_IO_HUP) { + return FALSE; + } + + return TRUE; +} + +static void +watch_for_smartcards (GdmSmartcardExtension *extension) +{ + GError *error; + GIOChannel *io_channel; + char *args[] = { GDM_SMARTCARD_WORKER_COMMAND, NULL }; + GPid pid; + int stdout_fd; + + error = NULL; + + if (!g_spawn_async_with_pipes (NULL, args, NULL, 0, + NULL, NULL, &pid, NULL, + &stdout_fd, NULL, &error)) { + g_debug ("could not start smart card manager: %s", error->message); + g_error_free (error); + return; + } + fcntl (stdout_fd, F_SETFD, FD_CLOEXEC); + + io_channel = g_io_channel_unix_new (stdout_fd); + g_io_channel_set_flags (io_channel, G_IO_FLAG_NONBLOCK, NULL); + g_io_channel_set_encoding (io_channel, NULL, NULL); + g_io_channel_set_buffered (io_channel, FALSE); + g_io_add_watch (io_channel, G_IO_IN, on_smartcard_event, extension); + g_io_channel_set_close_on_unref (io_channel, TRUE); + g_io_channel_unref (io_channel); + + extension->priv->worker_pid = pid; +} + +static void +stop_watching_for_smartcards (GdmSmartcardExtension *extension) +{ + kill (extension->priv->worker_pid, SIGTERM); +} + +static void +gdm_smartcard_extension_ask_question (GdmLoginExtension *login_extension, + const char *message) +{ + GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension); + gtk_widget_show (extension->priv->prompt_label); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); + gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE); + gtk_widget_show (extension->priv->prompt_entry); + gtk_action_set_visible (extension->priv->login_action, TRUE); + gtk_action_set_sensitive (extension->priv->login_action, TRUE); + gtk_widget_grab_focus (extension->priv->prompt_entry); + extension->priv->answer_pending = TRUE; +} + +static void +gdm_smartcard_extension_ask_secret (GdmLoginExtension *login_extension, + const char *message) +{ + GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension); + gtk_widget_show (extension->priv->prompt_label); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message); + gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), FALSE); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); + gtk_widget_show (extension->priv->prompt_entry); + gtk_widget_grab_focus (extension->priv->prompt_entry); + gtk_action_set_visible (extension->priv->login_action, TRUE); + gtk_action_set_sensitive (extension->priv->login_action, TRUE); + extension->priv->answer_pending = TRUE; +} + +static void +gdm_smartcard_extension_reset (GdmLoginExtension *login_extension) +{ + GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension); + gtk_widget_hide (extension->priv->prompt_label); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), ""); + + gtk_widget_hide (extension->priv->prompt_entry); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); + gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE); + gtk_action_set_visible (extension->priv->login_action, FALSE); + extension->priv->answer_pending = FALSE; + + purge_message_queue (extension); + set_message (extension, ""); + + gdm_login_extension_set_enabled (login_extension, FALSE); +} + +static void +gdm_smartcard_extension_set_ready (GdmLoginExtension *login_extension) +{ + GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension); + gdm_login_extension_set_enabled (login_extension, TRUE); + + if (extension->priv->worker_pid <= 0) { + watch_for_smartcards (extension); + } + + if (extension->priv->select_when_ready) { + if (_gdm_login_extension_emit_choose_user (login_extension, + GDM_SMARTCARD_EXTENSION_SERVICE_NAME)) { + extension->priv->select_when_ready = FALSE; + } + } +} + +static char * +gdm_smartcard_extension_get_service_name (GdmLoginExtension *login_extension) +{ + return g_strdup (GDM_SMARTCARD_EXTENSION_SERVICE_NAME); +} + +static GtkWidget * +gdm_smartcard_extension_get_page (GdmLoginExtension *login_extension) +{ + GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension); + return extension->priv->page; +} + +static GtkActionGroup * +gdm_smartcard_extension_get_actions (GdmLoginExtension *login_extension) +{ + GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension); + + return g_object_ref (extension->priv->actions); +} + +static void +request_answer (GdmSmartcardExtension *extension) +{ + const char *text; + + if (!extension->priv->answer_pending) { + _gdm_login_extension_emit_answer (GDM_LOGIN_EXTENSION (extension), NULL); + return; + } + + extension->priv->answer_pending = FALSE; + text = gtk_entry_get_text (GTK_ENTRY (extension->priv->prompt_entry)); + _gdm_login_extension_emit_answer (GDM_LOGIN_EXTENSION (extension), text); + + gtk_widget_hide (extension->priv->prompt_entry); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), ""); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); + gtk_action_set_visible (extension->priv->login_action, FALSE); +} + +static gboolean +gdm_smartcard_extension_focus (GdmLoginExtension *login_extension) +{ + GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension); + + if (!extension->priv->answer_pending) { + return FALSE; + } + + gtk_widget_grab_focus (extension->priv->prompt_entry); + return TRUE; +} + +static gboolean +gdm_smartcard_extension_has_queued_messages (GdmLoginExtension *login_extension) +{ + GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension); + + if (extension->priv->message_timeout_id != 0) { + return TRUE; + } + + if (!g_queue_is_empty (extension->priv->message_queue)) { + return TRUE; + } + + return FALSE; +} + + +static GIcon * +gdm_smartcard_extension_get_icon (GdmLoginExtension *login_extension) +{ + GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension); + return g_object_ref (extension->priv->icon); +} + +static char * +gdm_smartcard_extension_get_name (GdmLoginExtension *login_extension) +{ + return g_strdup (_("Smartcard Authentication")); +} + +static char * +gdm_smartcard_extension_get_description (GdmLoginExtension *login_extension) +{ + return g_strdup (_("Log into session with smartcard")); +} + +static gboolean +gdm_smartcard_extension_is_choosable (GdmLoginExtension *login_extension) +{ + return TRUE; +} + +static gboolean +gdm_smartcard_extension_is_visible (GdmLoginExtension *login_extension) +{ + GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (login_extension); + + char *contents, *pid_dir; + pid_t pid; + + if (!g_settings_get_boolean (extension->priv->settings, "enable-smartcard-authentication")) { + return FALSE; + } + + /* FIXME: we should rework things so we find out from the worker that + * there's no daemon running instead of like this. + */ + if (g_file_get_contents ("/var/run/pcscd.pid", + &contents, NULL, NULL) == FALSE) { + return FALSE; + } + + pid = (pid_t) atoi (contents); + g_free (contents); + + if (pid == 0) { + return FALSE; + } + + pid_dir = g_strdup_printf ("/proc/%d", (int) pid); + if (!g_file_test (pid_dir, G_FILE_TEST_EXISTS)) { + g_free (pid_dir); + return FALSE; + } + g_free (pid_dir); + + return TRUE; +} + +static void +gdm_login_extension_iface_init (GdmLoginExtensionIface *iface) +{ + iface->get_icon = gdm_smartcard_extension_get_icon; + iface->get_description = gdm_smartcard_extension_get_description; + iface->get_name = gdm_smartcard_extension_get_name; + iface->is_choosable = gdm_smartcard_extension_is_choosable; + iface->is_visible = gdm_smartcard_extension_is_visible; + iface->queue_message = gdm_smartcard_extension_queue_message; + iface->ask_question = gdm_smartcard_extension_ask_question; + iface->ask_secret = gdm_smartcard_extension_ask_secret; + iface->reset = gdm_smartcard_extension_reset; + iface->set_ready = gdm_smartcard_extension_set_ready; + iface->get_service_name = gdm_smartcard_extension_get_service_name; + iface->get_page = gdm_smartcard_extension_get_page; + iface->get_actions = gdm_smartcard_extension_get_actions; + iface->focus = gdm_smartcard_extension_focus; + iface->has_queued_messages = gdm_smartcard_extension_has_queued_messages; +} + +static void +gdm_smartcard_extension_class_init (GdmSmartcardExtensionClass *extension_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (extension_class); + + object_class->finalize = gdm_smartcard_extension_finalize; + + g_type_class_add_private (extension_class, + sizeof (GdmSmartcardExtensionPrivate)); +} + +static void +gdm_smartcard_extension_finalize (GObject *object) +{ + GdmSmartcardExtension *extension = GDM_SMARTCARD_EXTENSION (object); + + if (extension->priv->worker_pid > 0) { + stop_watching_for_smartcards (extension); + } + + purge_message_queue (extension); +} + +static void +create_page (GdmSmartcardExtension *extension) +{ + GtkBuilder *builder; + GObject *object; + GError *error; + + builder = gtk_builder_new (); + + error = NULL; + gtk_builder_add_from_file (builder, + PLUGINDATADIR "/page.ui", + &error); + + if (error != NULL) { + g_warning ("Could not load UI file: %s", error->message); + g_error_free (error); + return; + } + + object = gtk_builder_get_object (builder, "page"); + g_object_ref (object); + + extension->priv->page = GTK_WIDGET (object); + + object = gtk_builder_get_object (builder, "auth-prompt-label"); + g_object_ref (object); + extension->priv->prompt_label = GTK_WIDGET (object); + gtk_widget_hide (extension->priv->prompt_label); + + object = gtk_builder_get_object (builder, "auth-prompt-entry"); + g_object_ref (object); + extension->priv->prompt_entry = GTK_WIDGET (object); + gtk_widget_hide (extension->priv->prompt_entry); + + object = gtk_builder_get_object (builder, "auth-message-label"); + g_object_ref (object); + extension->priv->message_label = GTK_WIDGET (object); + gtk_widget_show (extension->priv->message_label); + + g_object_unref (builder); +} + +static void +on_activate_log_in (GdmSmartcardExtension *extension) +{ + request_answer (extension); + gtk_action_set_sensitive (extension->priv->login_action, FALSE); +} + +static void +create_actions (GdmSmartcardExtension *extension) +{ + GtkAction *action; + + extension->priv->actions = gtk_action_group_new (GDM_SMARTCARD_EXTENSION_NAME); + + action = gtk_action_new (GDM_LOGIN_EXTENSION_DEFAULT_ACTION, + _("Log In"), NULL, NULL); + g_signal_connect_swapped (action, "activate", + G_CALLBACK (on_activate_log_in), extension); + g_object_set (G_OBJECT (action), "icon-name", "go-home", NULL); + gtk_action_group_add_action (extension->priv->actions, + action); + + gtk_action_set_visible (action, FALSE); + extension->priv->login_action = action; +} + +static void +gdm_smartcard_extension_init (GdmSmartcardExtension *extension) +{ + extension->priv = G_TYPE_INSTANCE_GET_PRIVATE (extension, + GDM_TYPE_SMARTCARD_EXTENSION, + GdmSmartcardExtensionPrivate); + + extension->priv->icon = g_themed_icon_new ("gdm-smartcard"); + create_page (extension); + create_actions (extension); + + extension->priv->message_queue = g_queue_new (); + + extension->priv->settings = g_settings_new ("org.gnome.login-screen"); + + gdm_smartcard_extension_reset (GDM_LOGIN_EXTENSION (extension)); +} + +void +g_io_module_load (GIOModule *module) +{ + g_io_extension_point_implement (GDM_LOGIN_EXTENSION_POINT_NAME, + GDM_TYPE_SMARTCARD_EXTENSION, + GDM_SMARTCARD_EXTENSION_NAME, + 0); +} + +void +g_io_module_unload (GIOModule *module) +{ +} diff --git a/gui/simple-greeter/extensions/smartcard/gdm-smartcard-extension.h b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-extension.h new file mode 100644 index 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 <rstrode@redhat.com> + */ + +#ifndef __GDM_SMARTCARD_EXTENSION_H +#define __GDM_SMARTCARD_EXTENSION_H + +#include <glib-object.h> +#include "gdm-login-extension.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_SMARTCARD_EXTENSION (gdm_smartcard_extension_get_type ()) +#define GDM_SMARTCARD_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_SMARTCARD_EXTENSION, GdmSmartcardExtension)) +#define GDM_SMARTCARD_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_SMARTCARD_EXTENSION, GdmSmartcardExtensionClass)) +#define GDM_IS_SMARTCARD_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDM_TYPE_SMARTCARD_EXTENSION)) +#define GDM_IS_SMARTCARD_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDM_TYPE_SMARTCARD_EXTENSION)) +#define GDM_SMARTCARD_EXTENSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GDM_TYPE_SMARTCARD_EXTENSION, GdmSmartcardExtensionClass)) + +#define GDM_SMARTCARD_EXTENSION_NAME "gdm-smartcard-extension" + +typedef struct _GdmSmartcardExtensionPrivate GdmSmartcardExtensionPrivate; + +typedef struct +{ + GObject parent; + GdmSmartcardExtensionPrivate *priv; +} GdmSmartcardExtension; + +typedef struct +{ + GObjectClass parent_class; +} GdmSmartcardExtensionClass; + +GType gdm_smartcard_extension_get_type (void); + +G_END_DECLS + +#endif /* GDM_SMARTCARD_EXTENSION_H */ diff --git a/gui/simple-greeter/extensions/smartcard/gdm-smartcard-manager.c b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-manager.c new file mode 100644 index 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 <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <poll.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <glib.h> +#include <glib-unix.h> +#include <glib/gi18n.h> + +#include <prerror.h> +#include <prinit.h> +#include <nss.h> +#include <pk11func.h> +#include <secmod.h> +#include <secerr.h> + +#ifndef GDM_SMARTCARD_MANAGER_DRIVER +#define GDM_SMARTCARD_MANAGER_DRIVER LIBDIR"/pkcs11/libcoolkeypk11.so" +#endif + +#ifndef GDM_SMARTCARD_MANAGER_NSS_DB +#define GDM_SMARTCARD_MANAGER_NSS_DB SYSCONFDIR"/pki/nssdb" +#endif + +#ifndef GDM_MAX_OPEN_FILE_DESCRIPTORS +#define GDM_MAX_OPEN_FILE_DESCRIPTORS 1024 +#endif + +#ifndef GDM_OPEN_FILE_DESCRIPTORS_DIR +#define GDM_OPEN_FILE_DESCRIPTORS_DIR "/proc/self/fd" +#endif + +typedef enum _GdmSmartcardManagerState GdmSmartcardManagerState; +typedef struct _GdmSmartcardManagerWorker GdmSmartcardManagerWorker; + +enum _GdmSmartcardManagerState { + GDM_SMARTCARD_MANAGER_STATE_STOPPED = 0, + GDM_SMARTCARD_MANAGER_STATE_STARTING, + GDM_SMARTCARD_MANAGER_STATE_STARTED, + GDM_SMARTCARD_MANAGER_STATE_STOPPING, +}; + +struct _GdmSmartcardManagerPrivate { + GdmSmartcardManagerState state; + GList *modules; + char *module_path; + + GList *workers; + + GPid smartcard_event_watcher_pid; + GHashTable *smartcards; + + guint poll_timeout_id; + + guint32 is_unstoppable : 1; + guint32 nss_is_loaded : 1; +}; + +struct _GdmSmartcardManagerWorker { + GdmSmartcardManager *manager; + gint manager_fd; + + GThread *thread; + SECMODModule *module; + GHashTable *smartcards; + gint fd; + GSource *event_source; + + guint32 nss_is_loaded : 1; +}; + +static void gdm_smartcard_manager_finalize (GObject *object); +static void gdm_smartcard_manager_class_install_signals (GdmSmartcardManagerClass *service_class); +static void gdm_smartcard_manager_class_install_properties (GdmSmartcardManagerClass *service_class); +static void gdm_smartcard_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gdm_smartcard_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gdm_smartcard_manager_set_module_path (GdmSmartcardManager *manager, + const char *module_path); +static void gdm_smartcard_manager_card_removed_handler (GdmSmartcardManager *manager, + GdmSmartcard *card); +static void gdm_smartcard_manager_card_inserted_handler (GdmSmartcardManager *manager_class, + GdmSmartcard *card); +static gboolean gdm_smartcard_manager_stop_now (GdmSmartcardManager *manager); +static void gdm_smartcard_manager_queue_stop (GdmSmartcardManager *manager); + +static GdmSmartcardManagerWorker *gdm_smartcard_manager_create_worker (GdmSmartcardManager *manager, + SECMODModule *module); + +static GdmSmartcardManagerWorker * gdm_smartcard_manager_worker_new (GdmSmartcardManager *manager, + int worker_fd, + int manager_fd, + SECMODModule *module); +static void gdm_smartcard_manager_worker_free (GdmSmartcardManagerWorker *worker); +static gboolean sc_read_bytes (gint fd, gpointer bytes, gsize num_bytes); +static gboolean sc_write_bytes (gint fd, gconstpointer bytes, gsize num_bytes); +static GdmSmartcard *sc_read_smartcard (gint fd, SECMODModule *module); +static gboolean sc_write_smartcard (gint fd, GdmSmartcard *card); + +enum { + PROP_0 = 0, + PROP_MODULE_PATH, + NUMBER_OF_PROPERTIES +}; + +enum { + SMARTCARD_INSERTED = 0, + SMARTCARD_REMOVED, + ERROR, + NUMBER_OF_SIGNALS +}; + +static guint gdm_smartcard_manager_signals[NUMBER_OF_SIGNALS]; + +G_DEFINE_TYPE (GdmSmartcardManager, + gdm_smartcard_manager, + G_TYPE_OBJECT); + +static void +gdm_smartcard_manager_class_init (GdmSmartcardManagerClass *manager_class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (manager_class); + + gobject_class->finalize = gdm_smartcard_manager_finalize; + + gdm_smartcard_manager_class_install_signals (manager_class); + gdm_smartcard_manager_class_install_properties (manager_class); + + g_type_class_add_private (manager_class, + sizeof (GdmSmartcardManagerPrivate)); +} + +static void +gdm_smartcard_manager_class_install_properties (GdmSmartcardManagerClass *card_class) +{ + GObjectClass *object_class; + GParamSpec *param_spec; + + object_class = G_OBJECT_CLASS (card_class); + object_class->set_property = gdm_smartcard_manager_set_property; + object_class->get_property = gdm_smartcard_manager_get_property; + + param_spec = g_param_spec_string ("module-path", _("Module Path"), + _("path to smartcard PKCS #11 driver"), + NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_MODULE_PATH, param_spec); +} + +static void +gdm_smartcard_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmSmartcardManager *manager = GDM_SMARTCARD_MANAGER (object); + + switch (prop_id) { + case PROP_MODULE_PATH: + gdm_smartcard_manager_set_module_path (manager, + g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_smartcard_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmSmartcardManager *manager = GDM_SMARTCARD_MANAGER (object); + char *module_path; + + switch (prop_id) { + case PROP_MODULE_PATH: + module_path = gdm_smartcard_manager_get_module_path (manager); + g_value_set_string (value, module_path); + g_free (module_path); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +char * +gdm_smartcard_manager_get_module_path (GdmSmartcardManager *manager) +{ + return manager->priv->module_path; +} + +static void +gdm_smartcard_manager_set_module_path (GdmSmartcardManager *manager, + const char *module_path) +{ + if ((manager->priv->module_path == NULL) && (module_path == NULL)) { + return; + } + + if (((manager->priv->module_path == NULL) || + (module_path == NULL) || + (strcmp (manager->priv->module_path, module_path) != 0))) { + g_free (manager->priv->module_path); + manager->priv->module_path = g_strdup (module_path); + g_object_notify (G_OBJECT (manager), "module-path"); + } +} + +static void +gdm_smartcard_manager_card_removed_handler (GdmSmartcardManager *manager, + GdmSmartcard *card) +{ + g_debug ("informing smartcard of its removal"); + _gdm_smartcard_set_state (card, GDM_SMARTCARD_STATE_REMOVED); + g_debug ("done"); +} + +static void +gdm_smartcard_manager_card_inserted_handler (GdmSmartcardManager *manager, + GdmSmartcard *card) +{ + g_debug ("informing smartcard of its insertion"); + + _gdm_smartcard_set_state (card, GDM_SMARTCARD_STATE_INSERTED); + g_debug ("done"); + +} + +static void +gdm_smartcard_manager_class_install_signals (GdmSmartcardManagerClass *manager_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (manager_class); + + gdm_smartcard_manager_signals[SMARTCARD_INSERTED] = + g_signal_new ("smartcard-inserted", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmSmartcardManagerClass, + smartcard_inserted), + NULL, NULL, g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + manager_class->smartcard_inserted = gdm_smartcard_manager_card_inserted_handler; + + gdm_smartcard_manager_signals[SMARTCARD_REMOVED] = + g_signal_new ("smartcard-removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmSmartcardManagerClass, + smartcard_removed), + NULL, NULL, g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + manager_class->smartcard_removed = gdm_smartcard_manager_card_removed_handler; + + gdm_smartcard_manager_signals[ERROR] = + g_signal_new ("error", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmSmartcardManagerClass, error), + NULL, NULL, g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + manager_class->error = NULL; +} + +static gboolean +sc_slot_id_equal (CK_SLOT_ID *slot_id_1, + CK_SLOT_ID *slot_id_2) +{ + g_assert (slot_id_1 != NULL); + g_assert (slot_id_2 != NULL); + + return *slot_id_1 == *slot_id_2; +} + +static gboolean +sc_slot_id_hash (CK_SLOT_ID *slot_id) +{ + guint32 upper_bits, lower_bits; + gint temp; + + if (sizeof (CK_SLOT_ID) == sizeof (gint)) { + return g_int_hash (slot_id); + } + + upper_bits = ((*slot_id) >> 31) - 1; + lower_bits = (*slot_id) & 0xffffffff; + + /* The upper bits are almost certainly always zero, + * so let's degenerate to g_int_hash for the + * (very) common case + */ + temp = lower_bits + upper_bits; + return upper_bits + g_int_hash (&temp); +} + +static void +gdm_smartcard_manager_init (GdmSmartcardManager *manager) +{ + g_debug ("initializing smartcard manager"); + + manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, + GDM_TYPE_SMARTCARD_MANAGER, + GdmSmartcardManagerPrivate); + manager->priv->poll_timeout_id = 0; + manager->priv->is_unstoppable = FALSE; + + manager->priv->smartcards = + g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); +} + +static void +gdm_smartcard_manager_finalize (GObject *object) +{ + GdmSmartcardManager *manager; + GObjectClass *gobject_class; + + manager = GDM_SMARTCARD_MANAGER (object); + gobject_class = + G_OBJECT_CLASS (gdm_smartcard_manager_parent_class); + + gdm_smartcard_manager_stop_now (manager); + + g_hash_table_destroy (manager->priv->smartcards); + manager->priv->smartcards = NULL; + + gobject_class->finalize (object); +} + +GQuark +gdm_smartcard_manager_error_quark (void) +{ + static GQuark error_quark = 0; + + if (error_quark == 0) { + error_quark = g_quark_from_static_string ("gdm-smartcard-manager-error-quark"); + } + + return error_quark; +} + +GdmSmartcardManager * +gdm_smartcard_manager_new (const char *module_path) +{ + GdmSmartcardManager *instance; + + instance = GDM_SMARTCARD_MANAGER (g_object_new (GDM_TYPE_SMARTCARD_MANAGER, + "module-path", module_path, + NULL)); + + return instance; +} + +static void +gdm_smartcard_manager_emit_error (GdmSmartcardManager *manager, + GError *error) +{ + manager->priv->is_unstoppable = TRUE; + g_signal_emit (manager, gdm_smartcard_manager_signals[ERROR], 0, + error); + manager->priv->is_unstoppable = FALSE; +} + +static void +gdm_smartcard_manager_emit_smartcard_inserted (GdmSmartcardManager *manager, + GdmSmartcard *card) +{ + manager->priv->is_unstoppable = TRUE; + g_signal_emit (manager, gdm_smartcard_manager_signals[SMARTCARD_INSERTED], 0, + card); + manager->priv->is_unstoppable = FALSE; +} + +static void +gdm_smartcard_manager_emit_smartcard_removed (GdmSmartcardManager *manager, + GdmSmartcard *card) +{ + manager->priv->is_unstoppable = TRUE; + g_signal_emit (manager, gdm_smartcard_manager_signals[SMARTCARD_REMOVED], 0, + card); + manager->priv->is_unstoppable = FALSE; +} + +static gboolean +gdm_smartcard_manager_check_for_and_process_events (GIOChannel *io_channel, + GIOCondition condition, + GdmSmartcardManagerWorker *worker) +{ + GdmSmartcard *card; + GdmSmartcardManager *manager; + gboolean should_stop; + guchar event_type; + char *card_name; + gint fd; + + manager = worker->manager; + + g_debug ("event!"); + card = NULL; + should_stop = (condition & G_IO_HUP) || (condition & G_IO_ERR); + + if (should_stop) { + g_debug ("received %s on event socket, stopping " + "manager...", + (condition & G_IO_HUP) && (condition & G_IO_ERR)? + "error and hangup" : + (condition & G_IO_HUP)? + "hangup" : "error"); + } + + if (!(condition & G_IO_IN)) { + g_debug ("nevermind outta here!"); + goto out; + } + + fd = g_io_channel_unix_get_fd (io_channel); + + event_type = '\0'; + if (!sc_read_bytes (fd, &event_type, 1)) { + g_debug ("could not read event type, stopping"); + should_stop = TRUE; + goto out; + } + + card = sc_read_smartcard (fd, worker->module); + + if (card == NULL) { + g_debug ("could not read card, stopping"); + should_stop = TRUE; + goto out; + } + + card_name = gdm_smartcard_get_name (card); + g_debug ("card '%s' had event %c", card_name, event_type); + + switch (event_type) { + case 'I': + g_hash_table_replace (manager->priv->smartcards, + card_name, card); + card_name = NULL; + + gdm_smartcard_manager_emit_smartcard_inserted (manager, card); + card = NULL; + break; + + case 'R': + gdm_smartcard_manager_emit_smartcard_removed (manager, card); + if (!g_hash_table_remove (manager->priv->smartcards, card_name)) { + g_debug ("got removal event of unknown card!"); + } + g_free (card_name); + card_name = NULL; + card = NULL; + break; + + default: + g_free (card_name); + card_name = NULL; + g_object_unref (card); + + should_stop = TRUE; + break; + } + +out: + if (should_stop) { + GError *error; + + error = g_error_new (GDM_SMARTCARD_MANAGER_ERROR, + GDM_SMARTCARD_MANAGER_ERROR_WATCHING_FOR_EVENTS, + "%s", (condition & G_IO_IN) ? g_strerror (errno) : _("received error or hang up from event source")); + + gdm_smartcard_manager_emit_error (manager, error); + g_error_free (error); + gdm_smartcard_manager_stop_now (manager); + return FALSE; + } + + return TRUE; +} + +static void +stop_manager (GdmSmartcardManager *manager) +{ + manager->priv->state = GDM_SMARTCARD_MANAGER_STATE_STOPPED; + + if (manager->priv->nss_is_loaded) { + NSS_Shutdown (); + manager->priv->nss_is_loaded = FALSE; + } + g_debug ("smartcard manager stopped"); +} + +static void +stop_worker (GdmSmartcardManagerWorker *worker) +{ + GdmSmartcardManager *manager; + + manager = worker->manager; + + if (worker->event_source != NULL) { + g_source_destroy (worker->event_source); + worker->event_source = NULL; + } + + if (worker->thread != NULL) { + SECMOD_CancelWait (worker->module); + worker->thread = NULL; + } + + SECMOD_DestroyModule (worker->module); + manager->priv->workers = g_list_remove (manager->priv->workers, worker); + + if (manager->priv->workers == NULL && manager->priv->state != GDM_SMARTCARD_MANAGER_STATE_STOPPED) { + stop_manager (manager); + } +} + +static void +gdm_smartcard_manager_event_processing_stopped_handler (GdmSmartcardManagerWorker *worker) +{ + worker->event_source = NULL; + + stop_worker (worker); +} + +static void +gdm_smartcard_manager_stop_watching_for_events (GdmSmartcardManager *manager) +{ + GList *node; + + node = manager->priv->workers; + while (node != NULL) { + GdmSmartcardManagerWorker *worker; + GList *next_node; + + worker = (GdmSmartcardManagerWorker *) node->data; + next_node = node->next; + + stop_worker (worker); + + node = next_node; + } +} + +static gboolean +sc_load_nss (GError **error) +{ + SECStatus status = SECSuccess; + static const guint32 flags = + NSS_INIT_READONLY | + NSS_INIT_FORCEOPEN | NSS_INIT_NOROOTINIT | + NSS_INIT_OPTIMIZESPACE | NSS_INIT_PK11RELOAD; + + g_debug ("attempting to load NSS database '%s'", + GDM_SMARTCARD_MANAGER_NSS_DB); + + PR_Init (PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); + + status = NSS_Initialize (GDM_SMARTCARD_MANAGER_NSS_DB, + "", "", SECMOD_DB, flags); + + if (status != SECSuccess) { + gsize error_message_size; + char *error_message; + + error_message_size = PR_GetErrorTextLength (); + + if (error_message_size == 0) { + g_debug ("NSS security system could not be initialized"); + g_set_error (error, + GDM_SMARTCARD_MANAGER_ERROR, + GDM_SMARTCARD_MANAGER_ERROR_WITH_NSS, + _("NSS security system could not be initialized")); + goto out; + } + + error_message = g_slice_alloc0 (error_message_size); + PR_GetErrorText (error_message); + + g_set_error (error, + GDM_SMARTCARD_MANAGER_ERROR, + GDM_SMARTCARD_MANAGER_ERROR_WITH_NSS, + "%s", error_message); + g_debug ("NSS security system could not be initialized - %s", + error_message); + + g_slice_free1 (error_message_size, error_message); + + goto out; + } + + g_debug ("NSS database sucessfully loaded"); + return TRUE; + +out: + g_debug ("NSS database couldn't be sucessfully loaded"); + return FALSE; +} + +static GList * +get_available_modules (GdmSmartcardManager *manager) +{ + SECMODModuleList *module_list, *tmp; + GList *modules; + + g_debug ("Getting list of suitable modules"); + + module_list = SECMOD_GetDefaultModuleList (); + modules = NULL; + for (tmp = module_list; tmp != NULL; tmp = tmp->next) { + if (!SECMOD_HasRemovableSlots (tmp->module) || + !tmp->module->loaded) + continue; + + g_debug ("Using module '%s'", tmp->module->commonName); + + modules = g_list_prepend (modules, + SECMOD_ReferenceModule (tmp->module)); + } + + return modules; +} + +static gboolean +load_driver (GdmSmartcardManager *manager, + char *module_path, + GError **error) +{ + GList *modules; + char *module_spec; + gboolean module_explicitly_specified; + + g_debug ("attempting to load driver..."); + + modules = NULL; + module_explicitly_specified = module_path != NULL; + if (module_explicitly_specified) { + SECMODModule *module; + + module_spec = g_strdup_printf ("library=\"%s\"", module_path); + g_debug ("loading smartcard driver using spec '%s'", + module_spec); + + module = SECMOD_LoadUserModule (module_spec, + NULL /* parent */, + FALSE /* recurse */); + g_free (module_spec); + module_spec = NULL; + + if (!SECMOD_HasRemovableSlots (module) || + !module->loaded) { + modules = g_list_prepend (modules, module); + } else { + g_debug ("fallback module found but not %s", + SECMOD_HasRemovableSlots (module)? + "removable" : "loaded"); + SECMOD_DestroyModule (module); + } + + } else { + SECMODListLock *lock; + + lock = SECMOD_GetDefaultModuleListLock (); + + if (lock != NULL) { + SECMOD_GetReadLock (lock); + modules = get_available_modules (manager); + SECMOD_ReleaseReadLock (lock); + } + + /* fallback to compiled in driver path + */ + if (modules == NULL) { + SECMODModule *module; + module_path = GDM_SMARTCARD_MANAGER_DRIVER; + module_spec = g_strdup_printf ("library=\"%s\"", module_path); + g_debug ("loading smartcard driver using spec '%s'", + module_spec); + + module = SECMOD_LoadUserModule (module_spec, + NULL /* parent */, + FALSE /* recurse */); + g_free (module_spec); + module_spec = NULL; + + if (!SECMOD_HasRemovableSlots (module) || + !module->loaded) { + modules = g_list_prepend (modules, module); + } else { + g_debug ("fallback module found but not loaded"); + SECMOD_DestroyModule (module); + } + } + + } + + if (!module_explicitly_specified && modules == NULL) { + g_set_error (error, + GDM_SMARTCARD_MANAGER_ERROR, + GDM_SMARTCARD_MANAGER_ERROR_LOADING_DRIVER, + _("no suitable smartcard driver could be found")); + } else if (modules == NULL) { + + gsize error_message_size; + char *error_message; + + error_message_size = PR_GetErrorTextLength (); + + if (error_message_size == 0) { + g_debug ("smartcard driver '%s' could not be loaded", + module_path); + g_set_error (error, + GDM_SMARTCARD_MANAGER_ERROR, + GDM_SMARTCARD_MANAGER_ERROR_LOADING_DRIVER, + _("smartcard driver '%s' could not be " + "loaded"), module_path); + goto out; + } + + error_message = g_slice_alloc0 (error_message_size); + PR_GetErrorText (error_message); + + g_set_error (error, + GDM_SMARTCARD_MANAGER_ERROR, + GDM_SMARTCARD_MANAGER_ERROR_LOADING_DRIVER, + "%s", error_message); + + g_debug ("smartcard driver '%s' could not be loaded - %s", + module_path, error_message); + g_slice_free1 (error_message_size, error_message); + } + + manager->priv->modules = modules; +out: + return manager->priv->modules != NULL; +} + +static void +gdm_smartcard_manager_get_all_cards (GdmSmartcardManager *manager) +{ + GList *node; + int i; + + node = manager->priv->workers; + while (node != NULL) { + + GdmSmartcardManagerWorker *worker; + + worker = (GdmSmartcardManagerWorker *) node->data; + + for (i = 0; i < worker->module->slotCount; i++) { + GdmSmartcard *card; + CK_SLOT_ID slot_id; + gint slot_series; + char *card_name; + + slot_id = PK11_GetSlotID (worker->module->slots[i]); + slot_series = PK11_GetSlotSeries (worker->module->slots[i]); + + card = _gdm_smartcard_new (worker->module, + slot_id, slot_series); + + card_name = gdm_smartcard_get_name (card); + + g_hash_table_replace (manager->priv->smartcards, + card_name, card); + } + node = node->next; + } +} + +static GdmSmartcardManagerWorker * +start_worker (GdmSmartcardManager *manager, + SECMODModule *module, + GError **error) +{ + GIOChannel *io_channel; + GSource *source; + GdmSmartcardManagerWorker *worker; + + worker = gdm_smartcard_manager_create_worker (manager, module); + + if (worker == NULL) { + g_set_error (error, + GDM_SMARTCARD_MANAGER_ERROR, + GDM_SMARTCARD_MANAGER_ERROR_WATCHING_FOR_EVENTS, + _("could not watch for incoming card events - %s"), + g_strerror (errno)); + + goto out; + } + + io_channel = g_io_channel_unix_new (worker->manager_fd); + + source = g_io_create_watch (io_channel, G_IO_IN | G_IO_HUP); + g_io_channel_unref (io_channel); + io_channel = NULL; + + worker->event_source = source; + + g_source_set_callback (worker->event_source, + (GSourceFunc) (GIOFunc) + gdm_smartcard_manager_check_for_and_process_events, + worker, + (GDestroyNotify) + gdm_smartcard_manager_event_processing_stopped_handler); + g_source_attach (worker->event_source, NULL); + g_source_unref (worker->event_source); +out: + return worker; +} + +static void +start_workers (GdmSmartcardManager *manager) +{ + GList *node; + + node = manager->priv->modules; + while (node != NULL) { + SECMODModule *module; + GdmSmartcardManagerWorker *worker; + GError *error; + + module = (SECMODModule *) node->data; + + error = NULL; + worker = start_worker (manager, module, &error); + if (worker == NULL) { + g_warning ("%s", error->message); + g_error_free (error); + } else { + manager->priv->workers = g_list_prepend (manager->priv->workers, + worker); + } + node = node->next; + } +} + +gboolean +gdm_smartcard_manager_start (GdmSmartcardManager *manager, + GError **error) +{ + GError *nss_error; + + if (manager->priv->state == GDM_SMARTCARD_MANAGER_STATE_STARTED) { + g_debug ("smartcard manager already started"); + return TRUE; + } + + manager->priv->state = GDM_SMARTCARD_MANAGER_STATE_STARTING; + + nss_error = NULL; + if (!manager->priv->nss_is_loaded && !sc_load_nss (&nss_error)) { + g_propagate_error (error, nss_error); + goto out; + } + manager->priv->nss_is_loaded = TRUE; + + if (manager->priv->modules == NULL) { + if (!load_driver (manager, manager->priv->module_path, &nss_error)) { + g_propagate_error (error, nss_error); + goto out; + } + } + + start_workers (manager); + + /* populate the hash with cards that are already inserted + */ + gdm_smartcard_manager_get_all_cards (manager); + + manager->priv->state = GDM_SMARTCARD_MANAGER_STATE_STARTED; + +out: + /* don't leave it in a half started state + */ + if (manager->priv->state != GDM_SMARTCARD_MANAGER_STATE_STARTED) { + g_debug ("smartcard manager could not be completely started"); + gdm_smartcard_manager_stop (manager); + } else { + g_debug ("smartcard manager started"); + } + + return manager->priv->state == GDM_SMARTCARD_MANAGER_STATE_STARTED; +} + +static gboolean +gdm_smartcard_manager_stop_now (GdmSmartcardManager *manager) +{ + if (manager->priv->state == GDM_SMARTCARD_MANAGER_STATE_STOPPED) { + return FALSE; + } + + gdm_smartcard_manager_stop_watching_for_events (manager); + + return FALSE; +} + +static void +gdm_smartcard_manager_queue_stop (GdmSmartcardManager *manager) +{ + + manager->priv->state = GDM_SMARTCARD_MANAGER_STATE_STOPPING; + + g_idle_add ((GSourceFunc) gdm_smartcard_manager_stop_now, manager); +} + +void +gdm_smartcard_manager_stop (GdmSmartcardManager *manager) +{ + if (manager->priv->state == GDM_SMARTCARD_MANAGER_STATE_STOPPED) { + return; + } + + if (manager->priv->is_unstoppable) { + gdm_smartcard_manager_queue_stop (manager); + return; + } + + gdm_smartcard_manager_stop_now (manager); +} + +static GdmSmartcardManagerWorker * +gdm_smartcard_manager_worker_new (GdmSmartcardManager *manager, + gint worker_fd, + gint manager_fd, + SECMODModule *module) +{ + GdmSmartcardManagerWorker *worker; + + worker = g_slice_new0 (GdmSmartcardManagerWorker); + worker->manager = manager; + worker->fd = worker_fd; + worker->manager_fd = manager_fd; + worker->module = module; + + worker->smartcards = + g_hash_table_new_full ((GHashFunc) sc_slot_id_hash, + (GEqualFunc) sc_slot_id_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); + + return worker; +} + +static void +gdm_smartcard_manager_worker_free (GdmSmartcardManagerWorker *worker) +{ + if (worker->smartcards != NULL) { + g_hash_table_destroy (worker->smartcards); + worker->smartcards = NULL; + } + + g_slice_free (GdmSmartcardManagerWorker, worker); +} + +static gboolean +sc_read_bytes (gint fd, gpointer bytes, gsize num_bytes) +{ + size_t bytes_left; + size_t total_bytes_read; + ssize_t bytes_read; + + bytes_left = (size_t) num_bytes; + total_bytes_read = 0; + + do { + bytes_read = read (fd, (gchar *) bytes + total_bytes_read, bytes_left); + g_assert (bytes_read <= (ssize_t) bytes_left); + + if (bytes_read <= 0) { + if ((bytes_read < 0) && (errno == EINTR || errno == EAGAIN)) { + continue; + } + + bytes_left = 0; + } else { + bytes_left -= bytes_read; + total_bytes_read += bytes_read; + } + } while (bytes_left > 0); + + if (total_bytes_read < (size_t) num_bytes) { + return FALSE; + } + + return TRUE; +} + +static gboolean +sc_write_bytes (gint fd, gconstpointer bytes, gsize num_bytes) +{ + size_t bytes_left; + size_t total_bytes_written; + ssize_t bytes_written; + + bytes_left = (size_t) num_bytes; + total_bytes_written = 0; + + do { + bytes_written = write (fd, (gchar *) bytes + total_bytes_written, bytes_left); + g_assert (bytes_written <= (ssize_t) bytes_left); + + if (bytes_written <= 0) { + if ((bytes_written < 0) && (errno == EINTR || errno == EAGAIN)) { + continue; + } + + bytes_left = 0; + } else { + bytes_left -= bytes_written; + total_bytes_written += bytes_written; + } + } while (bytes_left > 0); + + if (total_bytes_written < (size_t) num_bytes) { + return FALSE; + } + + return TRUE; +} + +static GdmSmartcard * +sc_read_smartcard (gint fd, + SECMODModule *module) +{ + GdmSmartcard *card; + char *card_name; + gsize card_name_size; + + card_name_size = 0; + if (!sc_read_bytes (fd, &card_name_size, sizeof (card_name_size))) { + return NULL; + } + + card_name = g_slice_alloc0 (card_name_size); + if (!sc_read_bytes (fd, card_name, card_name_size)) { + g_slice_free1 (card_name_size, card_name); + return NULL; + } + card = _gdm_smartcard_new_from_name (module, card_name); + g_slice_free1 (card_name_size, card_name); + + return card; +} + +static gboolean +sc_write_smartcard (gint fd, + GdmSmartcard *card) +{ + gsize card_name_size; + char *card_name; + + card_name = gdm_smartcard_get_name (card); + card_name_size = strlen (card_name) + 1; + + if (!sc_write_bytes (fd, &card_name_size, sizeof (card_name_size))) { + g_free (card_name); + return FALSE; + } + + if (!sc_write_bytes (fd, card_name, card_name_size)) { + g_free (card_name); + return FALSE; + } + g_free (card_name); + + return TRUE; +} + +static gboolean +gdm_smartcard_manager_worker_emit_smartcard_removed (GdmSmartcardManagerWorker *worker, + GdmSmartcard *card, + GError **error) +{ + g_debug ("card '%s' removed!", gdm_smartcard_get_name (card)); + + if (!sc_write_bytes (worker->fd, "R", 1)) { + goto error_out; + } + + if (!sc_write_smartcard (worker->fd, card)) { + goto error_out; + } + + return TRUE; + +error_out: + g_set_error (error, GDM_SMARTCARD_MANAGER_ERROR, + GDM_SMARTCARD_MANAGER_ERROR_REPORTING_EVENTS, + "%s", g_strerror (errno)); + return FALSE; +} + +static gboolean +gdm_smartcard_manager_worker_emit_smartcard_inserted (GdmSmartcardManagerWorker *worker, + GdmSmartcard *card, + GError **error) +{ + + g_debug ("card '%s' inserted!", gdm_smartcard_get_name (card)); + if (!sc_write_bytes (worker->fd, "I", 1)) { + goto error_out; + } + + if (!sc_write_smartcard (worker->fd, card)) { + goto error_out; + } + + return TRUE; + +error_out: + g_set_error (error, GDM_SMARTCARD_MANAGER_ERROR, + GDM_SMARTCARD_MANAGER_ERROR_REPORTING_EVENTS, + "%s", g_strerror (errno)); + return FALSE; +} + +static gboolean +gdm_smartcard_manager_worker_watch_for_and_process_event (GdmSmartcardManagerWorker *worker, + GError **error) +{ + PK11SlotInfo *slot; + CK_SLOT_ID slot_id, *key; + gint slot_series, card_slot_series; + GdmSmartcard *card; + GError *processing_error; + + g_debug ("waiting for card event"); + + /* FIXME: we return FALSE quite a bit in this function without cleaning up + * resources. By returning FALSE we're going to ultimately exit anyway, but + * we should still be tidier about things. + */ + + slot = SECMOD_WaitForAnyTokenEvent (worker->module, 0, PR_SecondsToInterval (1)); + + processing_error = NULL; + + if (slot == NULL) { + int error_code; + + error_code = PORT_GetError (); + if ((error_code == 0) || (error_code == SEC_ERROR_NO_EVENT)) { + g_debug ("spurrious event occurred"); + return TRUE; + } + + /* FIXME: is there a function to convert from a PORT error + * code to a translated string? + */ + g_set_error (error, GDM_SMARTCARD_MANAGER_ERROR, + GDM_SMARTCARD_MANAGER_ERROR_WITH_NSS, + _("encountered unexpected error while " + "waiting for smartcard events")); + return FALSE; + } + + /* the slot id and series together uniquely identify a card. + * You can never have two cards with the same slot id at the + * same time, however (I think), so we can key off of it. + */ + slot_id = PK11_GetSlotID (slot); + slot_series = PK11_GetSlotSeries (slot); + + /* First check to see if there is a card that we're currently + * tracking in the slot. + */ + key = g_new (CK_SLOT_ID, 1); + *key = slot_id; + card = g_hash_table_lookup (worker->smartcards, key); + + if (card != NULL) { + card_slot_series = gdm_smartcard_get_slot_series (card); + } else { + card_slot_series = -1; + } + + if (PK11_IsPresent (slot)) { + /* Now, check to see if their is a new card in the slot. + * If there was a different card in the slot now than + * there was before, then we need to emit a removed signal + * for the old card (we don't want unpaired insertion events). + */ + if ((card != NULL) && + card_slot_series != slot_series) { + if (!gdm_smartcard_manager_worker_emit_smartcard_removed (worker, card, &processing_error)) { + g_propagate_error (error, processing_error); + return FALSE; + } + } + + card = _gdm_smartcard_new (worker->module, + slot_id, slot_series); + + g_hash_table_replace (worker->smartcards, + key, card); + key = NULL; + + if (!gdm_smartcard_manager_worker_emit_smartcard_inserted (worker, card, &processing_error)) { + g_propagate_error (error, processing_error); + return FALSE; + } + } else { + /* if we aren't tracking the card, just discard the event. + * We don't want unpaired remove events. Note on startup + * NSS will generate an "insertion" event if a card is + * already inserted in the slot. + */ + if ((card != NULL)) { + /* FIXME: i'm not sure about this code. Maybe we + * shouldn't do this at all, or maybe we should do it + * n times (where n = slot_series - card_slot_series + 1) + * + * Right now, i'm just doing it once. + */ + if ((slot_series - card_slot_series) > 1) { + + if (!gdm_smartcard_manager_worker_emit_smartcard_removed (worker, card, &processing_error)) { + g_propagate_error (error, processing_error); + return FALSE; + } + g_hash_table_remove (worker->smartcards, key); + + card = _gdm_smartcard_new (worker->module, + slot_id, slot_series); + g_hash_table_replace (worker->smartcards, + key, card); + key = NULL; + if (!gdm_smartcard_manager_worker_emit_smartcard_inserted (worker, card, &processing_error)) { + g_propagate_error (error, processing_error); + return FALSE; + } + } + + if (!gdm_smartcard_manager_worker_emit_smartcard_removed (worker, card, &processing_error)) { + g_propagate_error (error, processing_error); + return FALSE; + } + + g_hash_table_remove (worker->smartcards, key); + card = NULL; + } else { + g_debug ("got spurious remove event"); + } + } + + g_free (key); + PK11_FreeSlot (slot); + + return TRUE; +} + +static void +gdm_smartcard_manager_worker_run (GdmSmartcardManagerWorker *worker) +{ + GError *error; + gboolean should_continue; + + do + { + error = NULL; + should_continue = gdm_smartcard_manager_worker_watch_for_and_process_event (worker, &error); + } + while (should_continue); + + if (error != NULL) { + g_debug ("could not process card event - %s", error->message); + g_error_free (error); + } + + gdm_smartcard_manager_worker_free (worker); +} + +static GdmSmartcardManagerWorker * +gdm_smartcard_manager_create_worker (GdmSmartcardManager *manager, + SECMODModule *module) +{ + GdmSmartcardManagerWorker *worker; + gint pipefds[2]; + + if (!g_unix_open_pipe (pipefds, FD_CLOEXEC, NULL)) { + return FALSE; + } + + worker = gdm_smartcard_manager_worker_new (manager, + pipefds[1], + pipefds[0], + module); + + worker->thread = g_thread_new ("smartcard", + (GThreadFunc) gdm_smartcard_manager_worker_run, + worker); + + if (worker->thread == NULL) { + gdm_smartcard_manager_worker_free (worker); + return NULL; + } + + return worker; +} + +#ifdef GDM_SMARTCARD_MANAGER_ENABLE_TEST +#include <glib.h> + +static GMainLoop *event_loop; +static gboolean should_exit_on_next_remove = FALSE; + +static gboolean +on_timeout (GdmSmartcardManager *manager) +{ + GError *error = NULL; + g_print ("Re-enabling manager.\n"); + + if (!gdm_smartcard_manager_start (manager, &error)) { + g_warning ("could not start smartcard manager - %s", + error->message); + g_error_free (error); + return 1; + } + g_print ("Please re-insert smartcard\n"); + + should_exit_on_next_remove = TRUE; + + return FALSE; +} + +static void +on_device_inserted (GdmSmartcardManager *manager, + GdmSmartcard *card) +{ + g_print ("smartcard inserted!\n"); + g_print ("Please remove it.\n"); +} + +static void +on_device_removed (GdmSmartcardManager *manager, + GdmSmartcard *card) +{ + g_print ("smartcard removed!\n"); + + if (should_exit_on_next_remove) { + g_main_loop_quit (event_loop); + } else { + g_print ("disabling manager for 2 seconds\n"); + gdm_smartcard_manager_stop (manager); + g_timeout_add (2000, (GSourceFunc) on_timeout, manager); + } +} + +int +main (int argc, + char *argv[]) +{ + GdmSmartcardManager *manager; + GError *error; + + g_log_set_always_fatal (G_LOG_LEVEL_ERROR + | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING); + + g_message ("creating instance of 'smartcard manager' object..."); + manager = gdm_smartcard_manager_new (NULL); + g_message ("'smartcard manager' object created successfully"); + + g_signal_connect (manager, "smartcard-inserted", + G_CALLBACK (on_device_inserted), NULL); + + g_signal_connect (manager, "smartcard-removed", + G_CALLBACK (on_device_removed), NULL); + + g_message ("starting listener..."); + + error = NULL; + if (!gdm_smartcard_manager_start (manager, &error)) { + g_warning ("could not start smartcard manager - %s", + error->message); + g_error_free (error); + return 1; + } + + event_loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (event_loop); + g_main_loop_unref (event_loop); + event_loop = NULL; + + g_message ("destroying previously created 'smartcard manager' object..."); + g_object_unref (manager); + manager = NULL; + g_message ("'smartcard manager' object destroyed successfully"); + + return 0; +} +#endif diff --git a/gui/simple-greeter/extensions/smartcard/gdm-smartcard-manager.h b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-manager.h new file mode 100644 index 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 <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS +#define GDM_TYPE_SMARTCARD_MANAGER (gdm_smartcard_manager_get_type ()) +#define GDM_SMARTCARD_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_SMARTCARD_MANAGER, GdmSmartcardManager)) +#define GDM_SMARTCARD_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_SMARTCARD_MANAGER, GdmSmartcardManagerClass)) +#define GDM_IS_SMARTCARD_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SC_TYPE_SMARTCARD_MANAGER)) +#define GDM_IS_SMARTCARD_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SC_TYPE_SMARTCARD_MANAGER)) +#define GDM_SMARTCARD_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GDM_TYPE_SMARTCARD_MANAGER, GdmSmartcardManagerClass)) +#define GDM_SMARTCARD_MANAGER_ERROR (gdm_smartcard_manager_error_quark ()) +typedef struct _GdmSmartcardManager GdmSmartcardManager; +typedef struct _GdmSmartcardManagerClass GdmSmartcardManagerClass; +typedef struct _GdmSmartcardManagerPrivate GdmSmartcardManagerPrivate; +typedef enum _GdmSmartcardManagerError GdmSmartcardManagerError; + +struct _GdmSmartcardManager { + GObject parent; + + /*< private > */ + GdmSmartcardManagerPrivate *priv; +}; + +struct _GdmSmartcardManagerClass { + GObjectClass parent_class; + + /* Signals */ + void (*smartcard_inserted) (GdmSmartcardManager *manager, + GdmSmartcard *token); + void (*smartcard_removed) (GdmSmartcardManager *manager, + GdmSmartcard *token); + void (*error) (GdmSmartcardManager *manager, + GError *error); +}; + +enum _GdmSmartcardManagerError { + GDM_SMARTCARD_MANAGER_ERROR_GENERIC = 0, + GDM_SMARTCARD_MANAGER_ERROR_WITH_NSS, + GDM_SMARTCARD_MANAGER_ERROR_LOADING_DRIVER, + GDM_SMARTCARD_MANAGER_ERROR_WATCHING_FOR_EVENTS, + GDM_SMARTCARD_MANAGER_ERROR_REPORTING_EVENTS +}; + +GType gdm_smartcard_manager_get_type (void) G_GNUC_CONST; +GQuark gdm_smartcard_manager_error_quark (void) G_GNUC_CONST; + +GdmSmartcardManager *gdm_smartcard_manager_new (const char *module); + +gboolean gdm_smartcard_manager_start (GdmSmartcardManager *manager, + GError **error); + +void gdm_smartcard_manager_stop (GdmSmartcardManager *manager); + +char *gdm_smartcard_manager_get_module_path (GdmSmartcardManager *manager); +gboolean gdm_smartcard_manager_login_token_is_inserted (GdmSmartcardManager *manager); + +G_END_DECLS +#endif /* GDM_SMARTCARD_MANAGER_H */ diff --git a/gui/simple-greeter/extensions/smartcard/gdm-smartcard-worker.c b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-worker.c new file mode 100644 index 00000000..711c2c7f --- /dev/null +++ b/gui/simple-greeter/extensions/smartcard/gdm-smartcard-worker.c @@ -0,0 +1,184 @@ +#include "config.h" + +#include <fcntl.h> +#include <locale.h> +#include <sys/prctl.h> +#include <stdlib.h> +#include <unistd.h> + +#include <glib.h> + +#include "gdm-smartcard-manager.h" +#include "gdm-smartcard.h" + +#ifndef GDM_SMARTCARDS_CONF +#define GDM_SMARTCARDS_CONF GDMCONFDIR "/smartcards.conf" +#endif + +#ifndef GDM_SMARTCARDS_GROUP +#define GDM_SMARTCARDS_GROUP "Smartcards" +#endif + +#ifndef GDM_SMARTCARDS_KEY_ENABLED +#define GDM_SMARTCARDS_KEY_ENABLED "Enabled" +#endif + +#ifndef GDM_SMARTCARDS_KEY_DRIVER +#define GDM_SMARTCARDS_KEY_DRIVER "Driver" +#endif + +static GMainLoop *event_loop; +static GdmSmartcardManager *manager; +static int signal_pipe_fds[2] = { -1, -1 }; + +static void +on_smartcard_event (const char *event_string) +{ + g_debug ("smartcard event '%s' happened", event_string); + g_print ("%s", event_string); + fflush (stdout); +} + +static void +watch_for_smartcards (void) +{ + GError *error; + char *driver; + GKeyFile *cfg; + + cfg = g_key_file_new (); + + error = NULL; + driver = NULL; + if (g_key_file_load_from_file (cfg, GDM_SMARTCARDS_CONF, G_KEY_FILE_NONE, &error)) { + if (!g_key_file_get_boolean (cfg, GDM_SMARTCARDS_GROUP, GDM_SMARTCARDS_KEY_ENABLED, &error)) { + g_debug ("smartcard support is not enabled"); + goto out; + } + + driver = g_key_file_get_string (cfg, GDM_SMARTCARDS_GROUP, GDM_SMARTCARDS_KEY_DRIVER, NULL); + g_debug ("smartcards driver is set to '%s'", + driver == NULL || driver[0] == '\0'? "<automatic>" : driver); + } + + g_debug ("watching for smartcard insertion and removal events"); + manager = gdm_smartcard_manager_new (driver); + g_free (driver); + + g_signal_connect_swapped (manager, + "smartcard-inserted", + G_CALLBACK (on_smartcard_event), + "I"); + + g_signal_connect_swapped (manager, + "smartcard-removed", + G_CALLBACK (on_smartcard_event), + "R"); + + error = NULL; + if (!gdm_smartcard_manager_start (manager, &error)) { + g_object_unref (manager); + manager = NULL; + + if (error != NULL) { + g_debug ("%s", error->message); + g_error_free (error); + } else { + g_debug ("could not start smartcard manager"); + + } + goto out; + } +out: + g_key_file_free (cfg); +} + +static void +stop_watching_for_smartcards (void) +{ + if (manager != NULL) { + gdm_smartcard_manager_stop (manager); + g_object_unref (manager); + manager = NULL; + } +} + +static void +on_alrm_signal (int signal_number) +{ + raise (SIGKILL); +} + +static void +on_term_signal (int signal_number) +{ + close (signal_pipe_fds[1]); + signal_pipe_fds[1] = -1; + + /* Give us 10 seconds to clean up orderly. + * If that fails, then the smartcard stack + * is hung up and we need to die hard + */ + alarm (10); + signal (SIGALRM, on_alrm_signal); +} + +static gboolean +after_term_signal (GIOChannel *io_channel, + GIOCondition condition, + gpointer data) +{ + g_main_loop_quit (event_loop); + return FALSE; +} + +static void +on_debug_message (const char *log_domain, + GLogLevelFlags log_level, + const char *message, + gpointer user_data) +{ + g_printerr ("*** DEBUG: %s\n", message); +} + +int +main (int argc, + char **argv) +{ + GIOChannel *io_channel; + + setlocale (LC_ALL, ""); + + g_log_set_handler (NULL, G_LOG_LEVEL_DEBUG, on_debug_message, NULL); + + event_loop = g_main_loop_new (NULL, FALSE); + + watch_for_smartcards (); + + if (pipe (signal_pipe_fds) != 0) { + return 1; + } + fcntl (signal_pipe_fds[0], F_SETFD, FD_CLOEXEC); + fcntl (signal_pipe_fds[1], F_SETFD, FD_CLOEXEC); + + io_channel = g_io_channel_unix_new (signal_pipe_fds[0]); + g_io_channel_set_flags (io_channel, G_IO_FLAG_NONBLOCK, NULL); + g_io_channel_set_encoding (io_channel, NULL, NULL); + g_io_channel_set_buffered (io_channel, FALSE); + g_io_add_watch (io_channel, G_IO_HUP, after_term_signal, NULL); + g_io_channel_set_close_on_unref (io_channel, TRUE); + g_io_channel_unref (io_channel); + + signal (SIGTERM, on_term_signal); + signal (SIGPIPE, on_term_signal); + +#ifdef HAVE_SYS_PRCTL_H + prctl (PR_SET_PDEATHSIG, SIGKILL); +#endif + + g_main_loop_run (event_loop); + + stop_watching_for_smartcards (); + + return 0; +} diff --git a/gui/simple-greeter/extensions/smartcard/gdm-smartcard.c b/gui/simple-greeter/extensions/smartcard/gdm-smartcard.c new file mode 100644 index 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 <rstrode@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +#define GDM_SMARTCARD_ENABLE_INTERNAL_API +#include "gdm-smartcard.h" + +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include <cert.h> +#include <nss.h> +#include <pk11func.h> +#include <prerror.h> +#include <secmod.h> +#include <secerr.h> + +struct _GdmSmartcardPrivate { + SECMODModule *module; + GdmSmartcardState state; + + CK_SLOT_ID slot_id; + int slot_series; + + PK11SlotInfo *slot; + char *name; + + CERTCertificate *signing_certificate; + CERTCertificate *encryption_certificate; +}; + +static void gdm_smartcard_finalize (GObject *object); +static void gdm_smartcard_class_install_signals (GdmSmartcardClass *card_class); +static void gdm_smartcard_class_install_properties (GdmSmartcardClass *card_class); +static void gdm_smartcard_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gdm_smartcard_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gdm_smartcard_set_name (GdmSmartcard *card, const char *name); +static void gdm_smartcard_set_slot_id (GdmSmartcard *card, + int slot_id); +static void gdm_smartcard_set_slot_series (GdmSmartcard *card, + int slot_series); +static void gdm_smartcard_set_module (GdmSmartcard *card, + SECMODModule *module); + +static PK11SlotInfo *gdm_smartcard_find_slot_from_id (GdmSmartcard *card, + int slot_id); + +static PK11SlotInfo *gdm_smartcard_find_slot_from_card_name (GdmSmartcard *card, + const char *card_name); + +#ifndef GDM_SMARTCARD_DEFAULT_SLOT_ID +#define GDM_SMARTCARD_DEFAULT_SLOT_ID ((gulong) -1) +#endif + +#ifndef GDM_SMARTCARD_DEFAULT_SLOT_SERIES +#define GDM_SMARTCARD_DEFAULT_SLOT_SERIES -1 +#endif + +enum { + PROP_0 = 0, + PROP_NAME, + PROP_SLOT_ID, + PROP_SLOT_SERIES, + PROP_MODULE, + NUMBER_OF_PROPERTIES +}; + +enum { + INSERTED, + REMOVED, + NUMBER_OF_SIGNALS +}; + +static guint gdm_smartcard_signals[NUMBER_OF_SIGNALS]; + +G_DEFINE_TYPE (GdmSmartcard, gdm_smartcard, G_TYPE_OBJECT); + +static void +gdm_smartcard_class_init (GdmSmartcardClass *card_class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (card_class); + + gobject_class->finalize = gdm_smartcard_finalize; + + gdm_smartcard_class_install_signals (card_class); + gdm_smartcard_class_install_properties (card_class); + + g_type_class_add_private (card_class, + sizeof (GdmSmartcardPrivate)); +} + +static void +gdm_smartcard_class_install_signals (GdmSmartcardClass *card_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (card_class); + + gdm_smartcard_signals[INSERTED] = + g_signal_new ("inserted", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmSmartcardClass, + inserted), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gdm_smartcard_signals[REMOVED] = + g_signal_new ("removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmSmartcardClass, + removed), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +gdm_smartcard_class_install_properties (GdmSmartcardClass *card_class) +{ + GObjectClass *object_class; + GParamSpec *param_spec; + + object_class = G_OBJECT_CLASS (card_class); + object_class->set_property = gdm_smartcard_set_property; + object_class->get_property = gdm_smartcard_get_property; + + param_spec = g_param_spec_ulong ("slot-id", _("Slot ID"), + _("The slot the card is in"), + 1, G_MAXULONG, + GDM_SMARTCARD_DEFAULT_SLOT_ID, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_SLOT_ID, param_spec); + + param_spec = g_param_spec_int ("slot-series", _("Slot Series"), + _("per-slot card identifier"), + -1, G_MAXINT, + GDM_SMARTCARD_DEFAULT_SLOT_SERIES, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_SLOT_SERIES, param_spec); + + param_spec = g_param_spec_string ("name", _("name"), + _("name"), NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_NAME, param_spec); + + param_spec = g_param_spec_pointer ("module", _("Module"), + _("smartcard driver"), + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_MODULE, param_spec); +} + +static void +gdm_smartcard_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmSmartcard *card = GDM_SMARTCARD (object); + + switch (prop_id) { + case PROP_NAME: + gdm_smartcard_set_name (card, g_value_get_string (value)); + break; + + case PROP_SLOT_ID: + gdm_smartcard_set_slot_id (card, + g_value_get_ulong (value)); + break; + + case PROP_SLOT_SERIES: + gdm_smartcard_set_slot_series (card, + g_value_get_int (value)); + break; + + case PROP_MODULE: + gdm_smartcard_set_module (card, + (SECMODModule *) + g_value_get_pointer (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +CK_SLOT_ID +gdm_smartcard_get_slot_id (GdmSmartcard *card) +{ + return card->priv->slot_id; +} + +GdmSmartcardState +gdm_smartcard_get_state (GdmSmartcard *card) +{ + return card->priv->state; +} + +char * +gdm_smartcard_get_name (GdmSmartcard *card) +{ + return g_strdup (card->priv->name); +} + +gboolean +gdm_smartcard_is_login_card (GdmSmartcard *card) +{ + const char *login_card_name; + login_card_name = g_getenv ("PKCS11_LOGIN_TOKEN_NAME"); + + if ((login_card_name == NULL) || (card->priv->name == NULL)) { + return FALSE; + } + + if (strcmp (card->priv->name, login_card_name) == 0) { + return TRUE; + } + + return FALSE; +} + +static void +gdm_smartcard_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmSmartcard *card = GDM_SMARTCARD (object); + + switch (prop_id) { + case PROP_NAME: + g_value_take_string (value, + gdm_smartcard_get_name (card)); + break; + + case PROP_SLOT_ID: + g_value_set_ulong (value, + (gulong) gdm_smartcard_get_slot_id (card)); + break; + + case PROP_SLOT_SERIES: + g_value_set_int (value, + gdm_smartcard_get_slot_series (card)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gdm_smartcard_set_name (GdmSmartcard *card, + const char *name) +{ + if (name == NULL) { + return; + } + + if ((card->priv->name == NULL) || + (strcmp (card->priv->name, name) != 0)) { + g_free (card->priv->name); + card->priv->name = g_strdup (name); + + if (card->priv->slot == NULL) { + card->priv->slot = gdm_smartcard_find_slot_from_card_name (card, + card->priv->name); + + if (card->priv->slot != NULL) { + int slot_id, slot_series; + + slot_id = PK11_GetSlotID (card->priv->slot); + if (slot_id != card->priv->slot_id) { + gdm_smartcard_set_slot_id (card, slot_id); + } + + slot_series = PK11_GetSlotSeries (card->priv->slot); + if (slot_series != card->priv->slot_series) { + gdm_smartcard_set_slot_series (card, slot_series); + } + + _gdm_smartcard_set_state (card, GDM_SMARTCARD_STATE_INSERTED); + } else { + _gdm_smartcard_set_state (card, GDM_SMARTCARD_STATE_REMOVED); + } + } + + g_object_notify (G_OBJECT (card), "name"); + } +} + +static void +gdm_smartcard_set_slot_id (GdmSmartcard *card, + int slot_id) +{ + if (card->priv->slot_id != slot_id) { + card->priv->slot_id = slot_id; + + if (card->priv->slot == NULL) { + card->priv->slot = gdm_smartcard_find_slot_from_id (card, + card->priv->slot_id); + + if (card->priv->slot != NULL) { + const char *card_name; + + card_name = PK11_GetTokenName (card->priv->slot); + if ((card->priv->name == NULL) || + ((card_name != NULL) && + (strcmp (card_name, card->priv->name) != 0))) { + gdm_smartcard_set_name (card, card_name); + } + + _gdm_smartcard_set_state (card, GDM_SMARTCARD_STATE_INSERTED); + } else { + _gdm_smartcard_set_state (card, GDM_SMARTCARD_STATE_REMOVED); + } + } + + g_object_notify (G_OBJECT (card), "slot-id"); + } +} + +static void +gdm_smartcard_set_slot_series (GdmSmartcard *card, + int slot_series) +{ + if (card->priv->slot_series != slot_series) { + card->priv->slot_series = slot_series; + g_object_notify (G_OBJECT (card), "slot-series"); + } +} + +static void +gdm_smartcard_set_module (GdmSmartcard *card, + SECMODModule *module) +{ + gboolean should_notify; + + if (card->priv->module != module) { + should_notify = TRUE; + } else { + should_notify = FALSE; + } + + if (card->priv->module != NULL) { + SECMOD_DestroyModule (card->priv->module); + card->priv->module = NULL; + } + + if (module != NULL) { + card->priv->module = SECMOD_ReferenceModule (module); + } + + if (should_notify) { + g_object_notify (G_OBJECT (card), "module"); + } +} + +int +gdm_smartcard_get_slot_series (GdmSmartcard *card) +{ + return card->priv->slot_series; +} + +static void +gdm_smartcard_init (GdmSmartcard *card) +{ + + g_debug ("initializing smartcard "); + + card->priv = G_TYPE_INSTANCE_GET_PRIVATE (card, + GDM_TYPE_SMARTCARD, + GdmSmartcardPrivate); +} + +static void gdm_smartcard_finalize (GObject *object) +{ + GdmSmartcard *card; + GObjectClass *gobject_class; + + card = GDM_SMARTCARD (object); + + g_free (card->priv->name); + + gdm_smartcard_set_module (card, NULL); + + gobject_class = G_OBJECT_CLASS (gdm_smartcard_parent_class); + + gobject_class->finalize (object); +} + +GQuark gdm_smartcard_error_quark (void) +{ + static GQuark error_quark = 0; + + if (error_quark == 0) { + error_quark = g_quark_from_static_string ("gdm-smartcard-error-quark"); + } + + return error_quark; +} + +GdmSmartcard * +_gdm_smartcard_new (SECMODModule *module, + CK_SLOT_ID slot_id, + int slot_series) +{ + GdmSmartcard *card; + + g_return_val_if_fail (module != NULL, NULL); + g_return_val_if_fail (slot_id >= 1, NULL); + g_return_val_if_fail (slot_series > 0, NULL); + g_return_val_if_fail (sizeof (gulong) == sizeof (slot_id), NULL); + + card = GDM_SMARTCARD (g_object_new (GDM_TYPE_SMARTCARD, + "module", module, + "slot-id", (gulong) slot_id, + "slot-series", slot_series, + NULL)); + return card; +} + +GdmSmartcard * +_gdm_smartcard_new_from_name (SECMODModule *module, + const char *name) +{ + GdmSmartcard *card; + + g_return_val_if_fail (module != NULL, NULL); + g_return_val_if_fail (name != NULL, NULL); + + card = GDM_SMARTCARD (g_object_new (GDM_TYPE_SMARTCARD, + "module", module, + "name", name, + NULL)); + return card; +} + +void +_gdm_smartcard_set_state (GdmSmartcard *card, + GdmSmartcardState state) +{ + if (card->priv->state != state) { + card->priv->state = state; + + if (state == GDM_SMARTCARD_STATE_INSERTED) { + g_signal_emit (card, gdm_smartcard_signals[INSERTED], 0); + } else if (state == GDM_SMARTCARD_STATE_REMOVED) { + g_signal_emit (card, gdm_smartcard_signals[REMOVED], 0); + } else { + g_assert_not_reached (); + } + } +} + +/* So we could conceivably make the closure data a pointer to the card + * or something similiar and then emit signals when we want passwords, + * but it's probably easier to just get the password up front and use + * it. So we just take the passed in g_malloc'd (well probably, who knows) + * and strdup it using NSPR's memory allocation routines. + */ +static char * +gdm_smartcard_password_handler (PK11SlotInfo *slot, + PRBool is_retrying, + const char *password) +{ + if (is_retrying) { + return NULL; + } + + return password != NULL? PL_strdup (password): NULL; +} + +gboolean +gdm_smartcard_unlock (GdmSmartcard *card, + const char *password) +{ + SECStatus status; + + PK11_SetPasswordFunc ((PK11PasswordFunc) gdm_smartcard_password_handler); + + /* we pass PR_TRUE to load certificates + */ + status = PK11_Authenticate (card->priv->slot, PR_TRUE, (gpointer) password); + + if (status != SECSuccess) { + g_debug ("could not unlock card - %d", status); + return FALSE; + } + return TRUE; +} + +static PK11SlotInfo * +gdm_smartcard_find_slot_from_card_name (GdmSmartcard *card, + const char *card_name) +{ + int i; + + for (i = 0; i < card->priv->module->slotCount; i++) { + const char *slot_card_name; + + slot_card_name = PK11_GetTokenName (card->priv->module->slots[i]); + + if ((slot_card_name != NULL) && + (strcmp (slot_card_name, card_name) == 0)) { + return card->priv->module->slots[i]; + } + } + + return NULL; +} + +static PK11SlotInfo * +gdm_smartcard_find_slot_from_id (GdmSmartcard *card, + int slot_id) +{ + int i; + + for (i = 0; i < card->priv->module->slotCount; i++) { + if (PK11_GetSlotID (card->priv->module->slots[i]) == slot_id) { + return card->priv->module->slots[i]; + } + } + + return NULL; +} diff --git a/gui/simple-greeter/extensions/smartcard/gdm-smartcard.h b/gui/simple-greeter/extensions/smartcard/gdm-smartcard.h new file mode 100644 index 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 <glib.h> +#include <glib-object.h> + +#include <secmod.h> + +G_BEGIN_DECLS +#define GDM_TYPE_SMARTCARD (gdm_smartcard_get_type ()) +#define GDM_SMARTCARD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_SMARTCARD, GdmSmartcard)) +#define GDM_SMARTCARD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_SMARTCARD, GdmSmartcardClass)) +#define GDM_IS_SMARTCARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDM_TYPE_SMARTCARD)) +#define GDM_IS_SMARTCARD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDM_TYPE_SMARTCARD)) +#define GDM_SMARTCARD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GDM_TYPE_SMARTCARD, GdmSmartcardClass)) +#define GDM_SMARTCARD_ERROR (gdm_smartcard_error_quark ()) +typedef struct _GdmSmartcardClass GdmSmartcardClass; +typedef struct _GdmSmartcard GdmSmartcard; +typedef struct _GdmSmartcardPrivate GdmSmartcardPrivate; +typedef enum _GdmSmartcardError GdmSmartcardError; +typedef enum _GdmSmartcardState GdmSmartcardState; + +typedef struct _GdmSmartcardRequest GdmSmartcardRequest; + +struct _GdmSmartcard { + GObject parent; + + /*< private > */ + GdmSmartcardPrivate *priv; +}; + +struct _GdmSmartcardClass { + GObjectClass parent_class; + + void (* inserted) (GdmSmartcard *card); + void (* removed) (GdmSmartcard *card); +}; + +enum _GdmSmartcardError { + GDM_SMARTCARD_ERROR_GENERIC = 0, +}; + +enum _GdmSmartcardState { + GDM_SMARTCARD_STATE_INSERTED = 0, + GDM_SMARTCARD_STATE_REMOVED, +}; + +GType gdm_smartcard_get_type (void) G_GNUC_CONST; +GQuark gdm_smartcard_error_quark (void) G_GNUC_CONST; + +CK_SLOT_ID gdm_smartcard_get_slot_id (GdmSmartcard *card); +gint gdm_smartcard_get_slot_series (GdmSmartcard *card); +GdmSmartcardState gdm_smartcard_get_state (GdmSmartcard *card); + +char *gdm_smartcard_get_name (GdmSmartcard *card); +gboolean gdm_smartcard_is_login_card (GdmSmartcard *card); + +gboolean gdm_smartcard_unlock (GdmSmartcard *card, + const char *password); + +/* don't under any circumstances call these functions */ +#ifdef GDM_SMARTCARD_ENABLE_INTERNAL_API + +GdmSmartcard *_gdm_smartcard_new (SECMODModule *module, + CK_SLOT_ID slot_id, + gint slot_series); +GdmSmartcard *_gdm_smartcard_new_from_name (SECMODModule *module, + const char *name); + +void _gdm_smartcard_set_state (GdmSmartcard *card, + GdmSmartcardState state); +#endif + +G_END_DECLS +#endif /* GDM_SMARTCARD_H */ diff --git a/gui/simple-greeter/extensions/smartcard/icons/16x16/Makefile.am b/gui/simple-greeter/extensions/smartcard/icons/16x16/Makefile.am new file mode 100644 index 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 Binary files differnew file mode 100644 index 00000000..0112af1b --- /dev/null +++ b/gui/simple-greeter/extensions/smartcard/icons/16x16/gdm-smartcard.png 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 Binary files differnew file mode 100644 index 00000000..35d5578d --- /dev/null +++ b/gui/simple-greeter/extensions/smartcard/icons/48x48/gdm-smartcard.png 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 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.14"/> + <object class="GtkVBox" id="page"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkHBox" id="auth-input-box"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="auth-prompt-label"> + <property name="visible">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="auth-prompt-entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="activates_default">True</property> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="auth-message-box"> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="auth-message-label"> + <property name="visible">True</property> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> +</interface> diff --git a/gui/simple-greeter/extensions/unified/Makefile.am b/gui/simple-greeter/extensions/unified/Makefile.am new file mode 100644 index 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 <rstrode@redhat.com> + * + */ + +#include <config.h> +#include "gdm-unified-extension.h" +#include "gdm-login-extension.h" + +#include <glib/gi18n-lib.h> +#include <gio/gio.h> +#include <gtk/gtk.h> + +struct _GdmUnifiedExtensionPrivate +{ + GIcon *icon; + GtkWidget *page; + GtkActionGroup *actions; + GtkAction *login_action; + + GtkWidget *message_label; + GtkWidget *prompt_label; + GtkWidget *prompt_entry; + + GQueue *message_queue; + guint message_timeout_id; + + guint answer_pending : 1; +}; + +typedef struct { + char *text; + GdmServiceMessageType type; +} QueuedMessage; + +static void gdm_unified_extension_finalize (GObject *object); + +static void gdm_login_extension_iface_init (GdmLoginExtensionIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GdmUnifiedExtension, + gdm_unified_extension, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GDM_TYPE_LOGIN_EXTENSION, + gdm_login_extension_iface_init)); + +static void +set_message (GdmUnifiedExtension *extension, + const char *message) +{ + gtk_widget_show (extension->priv->message_label); + gtk_label_set_text (GTK_LABEL (extension->priv->message_label), message); +} + +static void +free_queued_message (QueuedMessage *message) +{ + g_free (message->text); + g_slice_free (QueuedMessage, message); +} + +static void +purge_message_queue (GdmUnifiedExtension *extension) +{ + if (extension->priv->message_timeout_id) { + g_source_remove (extension->priv->message_timeout_id); + extension->priv->message_timeout_id = 0; + } + g_queue_foreach (extension->priv->message_queue, + (GFunc) free_queued_message, + NULL); + g_queue_clear (extension->priv->message_queue); +} + +static gboolean +dequeue_message (GdmUnifiedExtension *extension) +{ + if (!g_queue_is_empty (extension->priv->message_queue)) { + int duration; + gboolean needs_beep; + + QueuedMessage *message; + message = (QueuedMessage *) g_queue_pop_head (extension->priv->message_queue); + + switch (message->type) { + case GDM_SERVICE_MESSAGE_TYPE_INFO: + needs_beep = FALSE; + break; + case GDM_SERVICE_MESSAGE_TYPE_PROBLEM: + needs_beep = TRUE; + break; + default: + g_assert_not_reached (); + } + + set_message (extension, message->text); + + duration = (int) (g_utf8_strlen (message->text, -1) / 66.0) * 1000; + duration = CLAMP (duration, 400, 3000); + + extension->priv->message_timeout_id = g_timeout_add (duration, + (GSourceFunc) dequeue_message, + extension); + if (needs_beep) { + gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (extension->priv->page))); + } + + free_queued_message (message); + } else { + extension->priv->message_timeout_id = 0; + + _gdm_login_extension_emit_message_queue_empty (GDM_LOGIN_EXTENSION (extension)); + } + + return FALSE; +} + +static void +gdm_unified_extension_queue_message (GdmLoginExtension *login_extension, + GdmServiceMessageType type, + const char *text) +{ + GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension); + + QueuedMessage *message = g_slice_new (QueuedMessage); + + message->text = g_strdup (text); + message->type = type; + + g_queue_push_tail (extension->priv->message_queue, message); + + if (extension->priv->message_timeout_id == 0) { + dequeue_message (extension); + } +} + +static void +gdm_unified_extension_ask_question (GdmLoginExtension *login_extension, + const char *message) +{ + GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension); + gtk_widget_show (extension->priv->prompt_label); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); + gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE); + gtk_widget_show (extension->priv->prompt_entry); + gtk_widget_grab_focus (extension->priv->prompt_entry); + extension->priv->answer_pending = TRUE; + + gtk_action_set_sensitive (extension->priv->login_action, TRUE); +} + +static void +gdm_unified_extension_ask_secret (GdmLoginExtension *login_extension, + const char *message) +{ + GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension); + gtk_widget_show (extension->priv->prompt_label); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), message); + gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), FALSE); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); + gtk_widget_show (extension->priv->prompt_entry); + gtk_widget_grab_focus (extension->priv->prompt_entry); + extension->priv->answer_pending = TRUE; + + gtk_action_set_sensitive (extension->priv->login_action, TRUE); +} + +static void +gdm_unified_extension_reset (GdmLoginExtension *login_extension) +{ + GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension); + gtk_widget_hide (extension->priv->prompt_label); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), ""); + + gtk_widget_hide (extension->priv->prompt_entry); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); + gtk_entry_set_visibility (GTK_ENTRY (extension->priv->prompt_entry), TRUE); + extension->priv->answer_pending = FALSE; + + set_message (extension, ""); + purge_message_queue (extension); + + gdm_login_extension_set_enabled (login_extension, FALSE); +} + +static void +gdm_unified_extension_set_ready (GdmLoginExtension *extension) +{ + gdm_login_extension_set_enabled (extension, TRUE); +} + +static char * +gdm_unified_extension_get_service_name (GdmLoginExtension *extension) +{ + return g_strdup (GDM_UNIFIED_EXTENSION_SERVICE_NAME); +} + +static GtkWidget * +gdm_unified_extension_get_page (GdmLoginExtension *login_extension) +{ + GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension); + return extension->priv->page; +} + +static GtkActionGroup * +gdm_unified_extension_get_actions (GdmLoginExtension *login_extension) +{ + GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension); + return g_object_ref (extension->priv->actions); +} + +static void +request_answer (GdmUnifiedExtension *extension) +{ + const char *text; + + if (!extension->priv->answer_pending) { + _gdm_login_extension_emit_answer (GDM_LOGIN_EXTENSION (extension), NULL); + return; + } + + extension->priv->answer_pending = FALSE; + text = gtk_entry_get_text (GTK_ENTRY (extension->priv->prompt_entry)); + _gdm_login_extension_emit_answer (GDM_LOGIN_EXTENSION (extension), text); + + gtk_widget_hide (extension->priv->prompt_entry); + gtk_widget_hide (extension->priv->prompt_label); + gtk_label_set_text (GTK_LABEL (extension->priv->prompt_label), ""); + gtk_entry_set_text (GTK_ENTRY (extension->priv->prompt_entry), ""); +} + +static gboolean +gdm_unified_extension_focus (GdmLoginExtension *login_extension) +{ + GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension); + if (!extension->priv->answer_pending) { + _gdm_login_extension_emit_answer (login_extension, NULL); + return FALSE; + } + + gtk_widget_grab_focus (extension->priv->prompt_entry); + return TRUE; +} + +static gboolean +gdm_unified_extension_has_queued_messages (GdmLoginExtension *login_extension) +{ + GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension); + + if (extension->priv->message_timeout_id != 0) { + return TRUE; + } + + if (!g_queue_is_empty (extension->priv->message_queue)) { + return TRUE; + } + + return FALSE; +} + +static GIcon * +gdm_unified_extension_get_icon (GdmLoginExtension *login_extension) +{ + GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (login_extension); + return g_object_ref (extension->priv->icon); +} + +static char * +gdm_unified_extension_get_name (GdmLoginExtension *login_extension) +{ + return g_strdup (_("Authentication")); +} + +static char * +gdm_unified_extension_get_description (GdmLoginExtension *login_extension) +{ + return g_strdup (_("Log into session")); +} + +static gboolean +gdm_unified_extension_is_choosable (GdmLoginExtension *login_extension) +{ + return FALSE; +} + +static gboolean +gdm_unified_extension_is_visible (GdmLoginExtension *login_extension) +{ + return TRUE; +} + +static void +gdm_login_extension_iface_init (GdmLoginExtensionIface *iface) +{ + iface->get_icon = gdm_unified_extension_get_icon; + iface->get_description = gdm_unified_extension_get_description; + iface->get_name = gdm_unified_extension_get_name; + iface->is_choosable = gdm_unified_extension_is_choosable; + iface->is_visible = gdm_unified_extension_is_visible; + iface->queue_message = gdm_unified_extension_queue_message; + iface->ask_question = gdm_unified_extension_ask_question; + iface->ask_secret = gdm_unified_extension_ask_secret; + iface->reset = gdm_unified_extension_reset; + iface->set_ready = gdm_unified_extension_set_ready; + iface->get_service_name = gdm_unified_extension_get_service_name; + iface->get_page = gdm_unified_extension_get_page; + iface->get_actions = gdm_unified_extension_get_actions; + iface->focus = gdm_unified_extension_focus; + iface->has_queued_messages = gdm_unified_extension_has_queued_messages; +} + +static void +gdm_unified_extension_class_init (GdmUnifiedExtensionClass *extension_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (extension_class); + + object_class->finalize = gdm_unified_extension_finalize; + + g_type_class_add_private (extension_class, + sizeof (GdmUnifiedExtensionPrivate)); +} + +static void +gdm_unified_extension_finalize (GObject *object) +{ + GdmUnifiedExtension *extension = GDM_UNIFIED_EXTENSION (object); + + purge_message_queue (extension); +} + +static void +on_activate_log_in (GdmUnifiedExtension *extension, + GtkAction *action) +{ + request_answer (extension); + gtk_action_set_sensitive (action, FALSE); +} + +static void +create_page (GdmUnifiedExtension *extension) +{ + GtkBuilder *builder; + GObject *object; + GError *error; + + builder = gtk_builder_new (); + + error = NULL; + gtk_builder_add_from_file (builder, + PLUGINDATADIR "/page.ui", + &error); + + if (error != NULL) { + g_warning ("Could not load UI file: %s", error->message); + g_error_free (error); + return; + } + + object = gtk_builder_get_object (builder, "page"); + g_object_ref (object); + + extension->priv->page = GTK_WIDGET (object); + + object = gtk_builder_get_object (builder, "auth-prompt-label"); + g_object_ref (object); + extension->priv->prompt_label = GTK_WIDGET (object); + gtk_widget_hide (extension->priv->prompt_label); + + object = gtk_builder_get_object (builder, "auth-prompt-entry"); + g_object_ref (object); + extension->priv->prompt_entry = GTK_WIDGET (object); + gtk_widget_hide (extension->priv->prompt_entry); + + object = gtk_builder_get_object (builder, "auth-message-label"); + g_object_ref (object); + extension->priv->message_label = GTK_WIDGET (object); + gtk_widget_show (extension->priv->message_label); + + g_object_unref (builder); +} + +static void +create_actions (GdmUnifiedExtension *extension) +{ + GtkAction *action; + + extension->priv->actions = gtk_action_group_new (GDM_UNIFIED_EXTENSION_NAME); + + action = gtk_action_new (GDM_LOGIN_EXTENSION_DEFAULT_ACTION, + _("Log In"), NULL, NULL); + g_signal_connect_swapped (action, "activate", + G_CALLBACK (on_activate_log_in), extension); + g_object_set (G_OBJECT (action), "icon-name", "go-home", NULL); + gtk_action_group_add_action (extension->priv->actions, + action); + + extension->priv->login_action = action; +} + +static void +gdm_unified_extension_init (GdmUnifiedExtension *extension) +{ + extension->priv = G_TYPE_INSTANCE_GET_PRIVATE (extension, + GDM_TYPE_UNIFIED_EXTENSION, + GdmUnifiedExtensionPrivate); + + extension->priv->icon = g_themed_icon_new ("dialog-unified"); + create_page (extension); + create_actions (extension); + + extension->priv->message_queue = g_queue_new (); + + gdm_unified_extension_reset (GDM_LOGIN_EXTENSION (extension)); +} + +void +gdm_unified_extension_load (void) +{ + g_io_extension_point_implement (GDM_LOGIN_EXTENSION_POINT_NAME, + GDM_TYPE_UNIFIED_EXTENSION, + GDM_UNIFIED_EXTENSION_NAME, + G_MAXINT); +} diff --git a/gui/simple-greeter/extensions/unified/gdm-unified-extension.h b/gui/simple-greeter/extensions/unified/gdm-unified-extension.h new file mode 100644 index 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 <rstrode@redhat.com> + */ + +#ifndef __GDM_UNIFIED_EXTENSION_H +#define __GDM_UNIFIED_EXTENSION_H + +#include <glib-object.h> +#include "gdm-login-extension.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_UNIFIED_EXTENSION (gdm_unified_extension_get_type ()) +#define GDM_UNIFIED_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_UNIFIED_EXTENSION, GdmUnifiedExtension)) +#define GDM_UNIFIED_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_UNIFIED_EXTENSION, GdmUnifiedExtensionClass)) +#define GDM_IS_UNIFIED_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDM_TYPE_UNIFIED_EXTENSION)) +#define GDM_IS_UNIFIED_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDM_TYPE_UNIFIED_EXTENSION)) +#define GDM_UNIFIED_EXTENSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GDM_TYPE_UNIFIED_EXTENSION, GdmUnifiedExtensionClass)) + +#define GDM_UNIFIED_EXTENSION_NAME "gdm-unified-extension" + +typedef struct _GdmUnifiedExtensionPrivate GdmUnifiedExtensionPrivate; + +typedef struct +{ + GObject parent; + GdmUnifiedExtensionPrivate *priv; +} GdmUnifiedExtension; + +typedef struct +{ + GObjectClass parent_class; +} GdmUnifiedExtensionClass; + +GType gdm_unified_extension_get_type (void); +void gdm_unified_extension_load (void); + +G_END_DECLS + +#endif /* GDM_UNIFIED_EXTENSION_H */ diff --git a/gui/simple-greeter/extensions/unified/gdm.pam b/gui/simple-greeter/extensions/unified/gdm.pam new file mode 100644 index 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 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.14"/> + <object class="GtkVBox" id="page"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkHBox" id="auth-input-box"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="auth-prompt-label"> + <property name="visible">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="auth-prompt-entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="activates_default">True</property> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="auth-message-box"> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="auth-message-label"> + <property name="visible">True</property> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> +</interface> diff --git a/gui/simple-greeter/gdm-cell-renderer-timer.c b/gui/simple-greeter/gdm-cell-renderer-timer.c new file mode 100644 index 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 <rstrode@redhat.com> + */ + +#include "config.h" +#include "gdm-cell-renderer-timer.h" +#include <glib/gi18n.h> + +#define GDM_CELL_RENDERER_TIMER_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), GDM_TYPE_CELL_RENDERER_TIMER, GdmCellRendererTimerPrivate)) + +struct _GdmCellRendererTimerPrivate +{ + gdouble value; +}; + +enum +{ + PROP_0, + PROP_VALUE, +}; + +G_DEFINE_TYPE (GdmCellRendererTimer, gdm_cell_renderer_timer, GTK_TYPE_CELL_RENDERER) + +static void +gdm_cell_renderer_timer_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GdmCellRendererTimer *renderer; + + renderer = GDM_CELL_RENDERER_TIMER (object); + + switch (param_id) { + case PROP_VALUE: + g_value_set_double (value, renderer->priv->value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + } +} + +static void +gdm_cell_renderer_timer_set_value (GdmCellRendererTimer *renderer, + gdouble value) +{ + renderer->priv->value = value; +} + +static void +gdm_cell_renderer_timer_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmCellRendererTimer *renderer; + + renderer = GDM_CELL_RENDERER_TIMER (object); + + switch (param_id) { + case PROP_VALUE: + gdm_cell_renderer_timer_set_value (renderer, + g_value_get_double (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + } +} + +static void +gdm_cell_renderer_timer_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + const GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height) +{ + + GdmCellRendererTimer *renderer; + + renderer = GDM_CELL_RENDERER_TIMER (cell); + + if (cell_area != NULL) { + if (x_offset != NULL) { + *x_offset = 0; + } + + if (y_offset != NULL) { + *y_offset = 0; + } + } + + gfloat xpad, ypad; + gtk_cell_renderer_get_alignment (cell, &xpad, &ypad); + + if (width != NULL) { + *width = xpad * 2 + 24; + } + + if (height != NULL) { + *height = ypad * 2 + 24; + } +} + +static double +get_opacity_for_value (double value) +{ + const double start_value = 0.05; + const double end_value = 0.33; + + if (value < start_value) { + return 0.0; + } + + if (value >= end_value) { + return 1.0; + } + + return ((value - start_value) / (end_value - start_value)); +} + +static void +draw_timer (GdmCellRendererTimer *renderer, + cairo_t *context, + GdkColor *fg, + GdkColor *bg, + int width, + int height) +{ + double radius; + double opacity; + + opacity = get_opacity_for_value (renderer->priv->value); + + if (opacity <= G_MINDOUBLE) { + return; + } + + radius = .5 * (MIN (width, height) / 2.0); + + cairo_translate (context, width / 2., height / 2.); + + cairo_set_source_rgba (context, + fg->red / 65535.0, + fg->green / 65535.0, + fg->blue / 65535.0, + opacity); + + cairo_move_to (context, 0, 0); + cairo_arc (context, 0, 0, radius + 1, 0, 2 * G_PI); + cairo_fill (context); + + cairo_set_source_rgb (context, + bg->red / 65535.0, + bg->green / 65535.0, + bg->blue / 65535.0); + cairo_move_to (context, 0, 0); + cairo_arc (context, 0, 0, radius, - G_PI / 2, + renderer->priv->value * 2 * G_PI - G_PI / 2); + cairo_clip (context); + cairo_paint_with_alpha (context, opacity); +} + +static void +gdm_cell_renderer_timer_render (GtkCellRenderer *cell, + cairo_t *context, + GtkWidget *widget, + const GdkRectangle *background_area, + const GdkRectangle *cell_area, + GtkCellRendererState renderer_state) +{ + GdmCellRendererTimer *renderer; + GtkStateType widget_state; + gfloat xpad, ypad; + + renderer = GDM_CELL_RENDERER_TIMER (cell); + + if (renderer->priv->value <= G_MINDOUBLE) { + return; + } + + gtk_cell_renderer_get_alignment (cell, &xpad, &ypad); + + cairo_translate (context, + cell_area->x + xpad, + cell_area->y + ypad); + + widget_state = GTK_STATE_NORMAL; + if (renderer_state & GTK_CELL_RENDERER_SELECTED) { + if (gtk_widget_has_focus (widget)) { + widget_state = GTK_STATE_SELECTED; + } else { + widget_state = GTK_STATE_ACTIVE; + } + } + + if (renderer_state & GTK_CELL_RENDERER_INSENSITIVE) { + widget_state = GTK_STATE_INSENSITIVE; + } + + draw_timer (renderer, context, + >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 <rstrode@redhat.com> + */ +#ifndef __GDM_CELL_RENDERER_TIMER_H +#define __GDM_CELL_RENDERER_TIMER_H + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_CELL_RENDERER_TIMER (gdm_cell_renderer_timer_get_type ()) +#define GDM_CELL_RENDERER_TIMER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDM_TYPE_CELL_RENDERER_TIMER, GdmCellRendererTimer)) +#define GDM_CELL_RENDERER_TIMER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_CELL_RENDERER_TIMER, GdmCellRendererTimerClass)) +#define GTK_IS_CELL_RENDERER_TIMER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDM_TYPE_CELL_RENDERER_TIMER)) +#define GTK_IS_CELL_RENDERER_TIMER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDM_TYPE_CELL_RENDERER_TIMER)) +#define GDM_CELL_RENDERER_TIMER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDM_TYPE_CELL_RENDERER_TIMER, GdmCellRendererTimerClass)) + +typedef struct _GdmCellRendererTimer GdmCellRendererTimer; +typedef struct _GdmCellRendererTimerClass GdmCellRendererTimerClass; +typedef struct _GdmCellRendererTimerPrivate GdmCellRendererTimerPrivate; + +struct _GdmCellRendererTimer +{ + GtkCellRenderer parent; + + /*< private >*/ + GdmCellRendererTimerPrivate *priv; +}; + +struct _GdmCellRendererTimerClass +{ + GtkCellRendererClass parent_class; +}; + +GType gdm_cell_renderer_timer_get_type (void); +GtkCellRenderer* gdm_cell_renderer_timer_new (void); + +G_END_DECLS + +#endif /* __GDM_CELL_RENDERER_TIMER_H */ diff --git a/gui/simple-greeter/gdm-chooser-widget.c b/gui/simple-greeter/gdm-chooser-widget.c new file mode 100644 index 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 <rstrode@redhat.com> + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <dirent.h> +#include <sys/stat.h> +#include <syslog.h> +#include <locale.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gtk/gtk.h> + +#include "gdm-chooser-widget.h" +#include "gdm-scrollable-widget.h" +#include "gdm-cell-renderer-timer.h" +#include "gdm-timer.h" + +#define GDM_CHOOSER_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_CHOOSER_WIDGET, GdmChooserWidgetPrivate)) + +#ifndef GDM_CHOOSER_WIDGET_DEFAULT_ICON_SIZE +#define GDM_CHOOSER_WIDGET_DEFAULT_ICON_SIZE 64 +#endif + +typedef enum { + GDM_CHOOSER_WIDGET_STATE_GROWN = 0, + GDM_CHOOSER_WIDGET_STATE_GROWING, + GDM_CHOOSER_WIDGET_STATE_SHRINKING, + GDM_CHOOSER_WIDGET_STATE_SHRUNK, +} GdmChooserWidgetState; + +struct GdmChooserWidgetPrivate +{ + GtkWidget *frame; + GtkWidget *frame_alignment; + GtkWidget *scrollable_widget; + + GtkWidget *items_view; + GtkListStore *list_store; + + GtkTreeModelFilter *model_filter; + GtkTreeModelSort *model_sorter; + + GdkPixbuf *is_in_use_pixbuf; + + /* row for the list_store model */ + GtkTreeRowReference *active_row; + GtkTreeRowReference *separator_row; + + GHashTable *rows_with_timers; + + GtkTreeViewColumn *status_column; + GtkTreeViewColumn *image_column; + + char *inactive_text; + char *active_text; + char *in_use_message; + + gint number_of_normal_rows; + gint number_of_separated_rows; + gint number_of_rows_with_status; + gint number_of_rows_with_images; + gint number_of_active_timers; + + guint update_idle_id; + guint update_separator_idle_id; + guint update_cursor_idle_id; + guint update_visibility_idle_id; + guint update_items_idle_id; + guint timer_animation_timeout_id; + + gboolean list_visible; + + guint32 should_hide_inactive_items : 1; + guint32 emit_activated_after_resize_animation : 1; + + GdmChooserWidgetPosition separator_position; + GdmChooserWidgetState state; + + double active_row_normalized_position; +}; + +enum { + PROP_0, + PROP_INACTIVE_TEXT, + PROP_ACTIVE_TEXT, + PROP_LIST_VISIBLE +}; + +enum { + ACTIVATED = 0, + DEACTIVATED, + LOADED, + NUMBER_OF_SIGNALS +}; + +static guint signals[NUMBER_OF_SIGNALS]; + +static void gdm_chooser_widget_class_init (GdmChooserWidgetClass *klass); +static void gdm_chooser_widget_init (GdmChooserWidget *chooser_widget); +static void gdm_chooser_widget_finalize (GObject *object); + +static void update_timer_from_time (GdmChooserWidget *widget, + GtkTreeRowReference *row, + double now); + +G_DEFINE_TYPE (GdmChooserWidget, gdm_chooser_widget, GTK_TYPE_ALIGNMENT) + +enum { + CHOOSER_IMAGE_COLUMN = 0, + CHOOSER_NAME_COLUMN, + CHOOSER_COMMENT_COLUMN, + CHOOSER_PRIORITY_COLUMN, + CHOOSER_ITEM_IS_IN_USE_COLUMN, + CHOOSER_ITEM_IS_SEPARATED_COLUMN, + CHOOSER_ITEM_IS_VISIBLE_COLUMN, + CHOOSER_TIMER_START_TIME_COLUMN, + CHOOSER_TIMER_DURATION_COLUMN, + CHOOSER_TIMER_VALUE_COLUMN, + CHOOSER_ID_COLUMN, + CHOOSER_LOAD_FUNC_COLUMN, + CHOOSER_LOAD_DATA_COLUMN, + NUMBER_OF_CHOOSER_COLUMNS +}; + +static gboolean +find_item (GdmChooserWidget *widget, + const char *id, + GtkTreeIter *iter) +{ + GtkTreeModel *model; + gboolean found_item; + + g_assert (GDM_IS_CHOOSER_WIDGET (widget)); + g_assert (id != NULL); + + found_item = FALSE; + model = GTK_TREE_MODEL (widget->priv->list_store); + + if (!gtk_tree_model_get_iter_first (model, iter)) { + return FALSE; + } + + do { + char *item_id; + + gtk_tree_model_get (model, + iter, + CHOOSER_ID_COLUMN, + &item_id, + -1); + + g_assert (item_id != NULL); + + if (strcmp (id, item_id) == 0) { + found_item = TRUE; + } + g_free (item_id); + + } while (!found_item && gtk_tree_model_iter_next (model, iter)); + + return found_item; +} + +typedef struct { + GdmChooserWidget *widget; + GdmChooserUpdateForeachFunc func; + gpointer user_data; +} UpdateForeachData; + +static gboolean +foreach_item (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + UpdateForeachData *data) +{ + GdkPixbuf *image; + char *name; + char *comment; + gboolean in_use; + gboolean is_separate; + gboolean res; + char *id; + gulong priority; + + gtk_tree_model_get (model, + iter, + CHOOSER_ID_COLUMN, &id, + CHOOSER_IMAGE_COLUMN, &image, + CHOOSER_NAME_COLUMN, &name, + CHOOSER_COMMENT_COLUMN, &comment, + CHOOSER_PRIORITY_COLUMN, &priority, + CHOOSER_ITEM_IS_IN_USE_COLUMN, &in_use, + CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate, + -1); + res = data->func (data->widget, + (const char *)id, + &image, + &name, + &comment, + &priority, + &in_use, + &is_separate, + data->user_data); + if (res) { + gtk_list_store_set (GTK_LIST_STORE (model), + iter, + CHOOSER_ID_COLUMN, id, + CHOOSER_IMAGE_COLUMN, image, + CHOOSER_NAME_COLUMN, name, + CHOOSER_COMMENT_COLUMN, comment, + CHOOSER_PRIORITY_COLUMN, priority, + CHOOSER_ITEM_IS_IN_USE_COLUMN, in_use, + CHOOSER_ITEM_IS_SEPARATED_COLUMN, is_separate, + -1); + } + + g_free (name); + g_free (comment); + if (image != NULL) { + g_object_unref (image); + } + + return FALSE; +} + +void +gdm_chooser_widget_update_foreach_item (GdmChooserWidget *widget, + GdmChooserUpdateForeachFunc func, + gpointer user_data) +{ + UpdateForeachData fdata; + + fdata.widget = widget; + fdata.func = func; + fdata.user_data = user_data; + gtk_tree_model_foreach (GTK_TREE_MODEL (widget->priv->list_store), + (GtkTreeModelForeachFunc) foreach_item, + &fdata); +} + +static void +translate_list_path_to_view_path (GdmChooserWidget *widget, + GtkTreePath **path) +{ + GtkTreePath *filtered_path; + GtkTreePath *sorted_path; + + /* the child model is the source for the filter */ + filtered_path = gtk_tree_model_filter_convert_child_path_to_path (widget->priv->model_filter, + *path); + sorted_path = gtk_tree_model_sort_convert_child_path_to_path (widget->priv->model_sorter, + filtered_path); + gtk_tree_path_free (filtered_path); + + gtk_tree_path_free (*path); + *path = sorted_path; +} + + +static void +translate_view_path_to_list_path (GdmChooserWidget *widget, + GtkTreePath **path) +{ + GtkTreePath *filtered_path; + GtkTreePath *list_path; + + /* the child model is the source for the filter */ + filtered_path = gtk_tree_model_sort_convert_path_to_child_path (widget->priv->model_sorter, + *path); + + list_path = gtk_tree_model_filter_convert_path_to_child_path (widget->priv->model_filter, + filtered_path); + gtk_tree_path_free (filtered_path); + + gtk_tree_path_free (*path); + *path = list_path; +} + +static GtkTreePath * +get_list_path_to_active_row (GdmChooserWidget *widget) +{ + GtkTreePath *path; + + if (widget->priv->active_row == NULL) { + return NULL; + } + + path = gtk_tree_row_reference_get_path (widget->priv->active_row); + if (path == NULL) { + return NULL; + } + + return path; +} + +static GtkTreePath * +get_view_path_to_active_row (GdmChooserWidget *widget) +{ + GtkTreePath *path; + + path = get_list_path_to_active_row (widget); + if (path == NULL) { + return NULL; + } + + translate_list_path_to_view_path (widget, &path); + + return path; +} + +static char * +get_active_item_id (GdmChooserWidget *widget, + GtkTreeIter *iter) +{ + char *item_id; + GtkTreeModel *model; + GtkTreePath *path; + + g_return_val_if_fail (GDM_IS_CHOOSER_WIDGET (widget), NULL); + + model = GTK_TREE_MODEL (widget->priv->list_store); + item_id = NULL; + + if (widget->priv->active_row == NULL) { + return NULL; + } + + path = get_list_path_to_active_row (widget); + if (path == NULL) { + return NULL; + } + + if (gtk_tree_model_get_iter (model, iter, path)) { + gtk_tree_model_get (model, + iter, + CHOOSER_ID_COLUMN, + &item_id, + -1); + } + gtk_tree_path_free (path); + + return item_id; +} + +char * +gdm_chooser_widget_get_active_item (GdmChooserWidget *widget) +{ + GtkTreeIter iter; + + return get_active_item_id (widget, &iter); +} + +static void +get_selected_list_path (GdmChooserWidget *widget, + GtkTreePath **pathp) +{ + GtkTreeSelection *selection; + GtkTreeModel *sort_model; + GtkTreeIter sorted_iter; + GtkTreePath *path; + + path = NULL; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view)); + if (gtk_tree_selection_get_selected (selection, &sort_model, &sorted_iter)) { + + g_assert (sort_model == GTK_TREE_MODEL (widget->priv->model_sorter)); + + path = gtk_tree_model_get_path (sort_model, &sorted_iter); + + translate_view_path_to_list_path (widget, &path); + } else { + g_debug ("GdmChooserWidget: no rows selected"); + } + + *pathp = path; +} + +char * +gdm_chooser_widget_get_selected_item (GdmChooserWidget *widget) +{ + GtkTreeIter iter; + GtkTreeModel *model; + GtkTreePath *path; + char *id; + + id = NULL; + + get_selected_list_path (widget, &path); + + if (path == NULL) { + return NULL; + } + + model = GTK_TREE_MODEL (widget->priv->list_store); + + if (gtk_tree_model_get_iter (model, &iter, path)) { + gtk_tree_model_get (model, + &iter, + CHOOSER_ID_COLUMN, + &id, + -1); + } + + gtk_tree_path_free (path); + + return id; +} + +void +gdm_chooser_widget_set_selected_item (GdmChooserWidget *widget, + const char *id) +{ + GtkTreeIter iter; + GtkTreeSelection *selection; + GtkTreeModel *model; + + g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); + + g_debug ("GdmChooserWidget: setting selected item '%s'", + id ? id : "(null)"); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view)); + + model = GTK_TREE_MODEL (widget->priv->list_store); + + if (find_item (widget, id, &iter)) { + GtkTreePath *path; + + path = gtk_tree_model_get_path (model, &iter); + translate_list_path_to_view_path (widget, &path); + + gtk_tree_selection_select_path (selection, path); + + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget->priv->items_view), + path, + NULL, + TRUE, + 0.5, + 0.0); + + gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view), + path, + NULL, + FALSE); + gtk_tree_path_free (path); + } else { + gtk_tree_selection_unselect_all (selection); + } +} + +static void +activate_from_item_id (GdmChooserWidget *widget, + const char *item_id) +{ + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + char *path_str; + + model = GTK_TREE_MODEL (widget->priv->list_store); + path = NULL; + + if (find_item (widget, item_id, &iter)) { + path = gtk_tree_model_get_path (model, &iter); + + path_str = gtk_tree_path_to_string (path); + g_debug ("GdmChooserWidget: got list path '%s'", path_str); + g_free (path_str); + + translate_list_path_to_view_path (widget, &path); + + path_str = gtk_tree_path_to_string (path); + g_debug ("GdmChooserWidget: translated to view path '%s'", path_str); + g_free (path_str); + } + + if (path == NULL) { + g_debug ("GdmChooserWidget: unable to activate - path for item '%s' not found", item_id); + return; + } + + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget->priv->items_view), + path, + NULL, + TRUE, + 0.5, + 0.0); + + gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view), + path, + NULL, + FALSE); + + gtk_tree_view_row_activated (GTK_TREE_VIEW (widget->priv->items_view), + path, + NULL); + gtk_tree_path_free (path); +} + +static void +set_frame_text (GdmChooserWidget *widget, + const char *text) +{ + GtkWidget *label; + + label = gtk_frame_get_label_widget (GTK_FRAME (widget->priv->frame)); + + if (text == NULL && label != NULL) { + gtk_frame_set_label_widget (GTK_FRAME (widget->priv->frame), + NULL); + gtk_alignment_set_padding (GTK_ALIGNMENT (widget->priv->frame_alignment), + 0, 0, 0, 0); + } else if (text != NULL && label == NULL) { + label = gtk_label_new (""); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), + widget->priv->items_view); + gtk_widget_show (label); + gtk_frame_set_label_widget (GTK_FRAME (widget->priv->frame), + label); + gtk_alignment_set_padding (GTK_ALIGNMENT (widget->priv->frame_alignment), + 0, 0, 0, 0); + } + + if (label != NULL && text != NULL) { + char *markup; + markup = g_strdup_printf ("%s", text); + gtk_label_set_markup_with_mnemonic (GTK_LABEL (label), markup); + g_free (markup); + } +} + +static void +on_shrink_animation_step (GdmScrollableWidget *scrollable_widget, + double progress, + int *new_height, + GdmChooserWidget *widget) +{ + GtkTreePath *active_row_path; + const double final_alignment = 0.5; + double row_alignment; + + active_row_path = get_view_path_to_active_row (widget); + row_alignment = widget->priv->active_row_normalized_position + progress * (final_alignment - widget->priv->active_row_normalized_position); + + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget->priv->items_view), + active_row_path, NULL, TRUE, row_alignment, 0.0); + gtk_tree_path_free (active_row_path); +} + +static gboolean +update_separator_visibility (GdmChooserWidget *widget) +{ + GtkTreePath *separator_path; + GtkTreeIter iter; + gboolean is_visible; + + g_debug ("GdmChooserWidget: updating separator visibility"); + + separator_path = gtk_tree_row_reference_get_path (widget->priv->separator_row); + + if (separator_path == NULL) { + goto out; + } + + gtk_tree_model_get_iter (GTK_TREE_MODEL (widget->priv->list_store), + &iter, separator_path); + + if (widget->priv->number_of_normal_rows > 0 && + widget->priv->number_of_separated_rows > 0 && + widget->priv->state != GDM_CHOOSER_WIDGET_STATE_SHRUNK) { + is_visible = TRUE; + } else { + is_visible = FALSE; + } + + gtk_list_store_set (widget->priv->list_store, + &iter, + CHOOSER_ITEM_IS_VISIBLE_COLUMN, is_visible, + -1); + + out: + widget->priv->update_separator_idle_id = 0; + return FALSE; +} + +static void +queue_update_separator_visibility (GdmChooserWidget *widget) +{ + if (widget->priv->update_separator_idle_id == 0) { + g_debug ("GdmChooserWidget: queuing update separator visibility"); + + widget->priv->update_separator_idle_id = + g_idle_add ((GSourceFunc) update_separator_visibility, widget); + } +} + +static gboolean +update_visible_items (GdmChooserWidget *widget) +{ + GtkTreePath *path; + GtkTreePath *end; + GtkTreeIter iter; + + if (! gtk_tree_view_get_visible_range (GTK_TREE_VIEW (widget->priv->items_view), &path, &end)) { + g_debug ("GdmChooserWidget: Unable to get visible range"); + goto out; + } + + for (; gtk_tree_path_compare (path, end) <= 0; gtk_tree_path_next (path)) { + char *id; + gpointer user_data; + GdmChooserWidgetItemLoadFunc func; + + if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (widget->priv->model_sorter), &iter, path)) + break; + + id = NULL; + gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->model_sorter), + &iter, + CHOOSER_ID_COLUMN, &id, + CHOOSER_LOAD_FUNC_COLUMN, &func, + CHOOSER_LOAD_DATA_COLUMN, &user_data, + -1); + if (id != NULL && func != NULL) { + GtkTreeIter child_iter; + GtkTreeIter list_iter; + + g_debug ("Updating for %s", id); + + gtk_tree_model_sort_convert_iter_to_child_iter (widget->priv->model_sorter, + &child_iter, + &iter); + + gtk_tree_model_filter_convert_iter_to_child_iter (widget->priv->model_filter, + &list_iter, + &child_iter); + /* remove the func so it doesn't need to load again */ + gtk_list_store_set (GTK_LIST_STORE (widget->priv->list_store), + &list_iter, + CHOOSER_LOAD_FUNC_COLUMN, NULL, + -1); + + func (widget, id, user_data); + } + + g_free (id); + } + + gtk_tree_path_free (path); + gtk_tree_path_free (end); + out: + widget->priv->update_items_idle_id = 0; + + return FALSE; +} + +static void +set_chooser_list_visible (GdmChooserWidget *widget, + gboolean is_visible) +{ + if (widget->priv->list_visible != is_visible) { + widget->priv->list_visible = is_visible; + g_object_notify (G_OBJECT (widget), "list-visible"); + } +} + +static gboolean +update_chooser_visibility (GdmChooserWidget *widget) +{ + update_visible_items (widget); + + if (gdm_chooser_widget_get_number_of_items (widget) > 0) { + gtk_widget_show (widget->priv->frame); + set_chooser_list_visible (widget, gtk_widget_get_visible (GTK_WIDGET (widget))); + } else { + gtk_widget_hide (widget->priv->frame); + set_chooser_list_visible (widget, FALSE); + } + + widget->priv->update_visibility_idle_id = 0; + + return FALSE; +} + +static inline gboolean +iters_equal (GtkTreeIter *a, + GtkTreeIter *b) +{ + if (a->stamp != b->stamp) + return FALSE; + + if (a->user_data != b->user_data) + return FALSE; + + /* user_data2 and user_data3 are not used in GtkListStore */ + + return TRUE; +} + +static void +set_inactive_items_visible (GdmChooserWidget *widget, + gboolean should_show) +{ + GtkTreeModel *model; + GtkTreeModel *view_model; + char *active_item_id; + GtkTreeIter active_item_iter; + GtkTreeIter iter; + + g_debug ("setting inactive items visible"); + + active_item_id = get_active_item_id (widget, &active_item_iter); + if (active_item_id == NULL) { + g_debug ("GdmChooserWidget: No active item set"); + } + + model = GTK_TREE_MODEL (widget->priv->list_store); + + if (!gtk_tree_model_get_iter_first (model, &iter)) { + goto out; + } + + /* unset tree view model to hide row add/remove signals from gail */ + view_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->items_view)); + g_object_ref (view_model); + gtk_tree_view_set_model (GTK_TREE_VIEW (widget->priv->items_view), NULL); + + g_debug ("GdmChooserWidget: Setting inactive items visible: %s", should_show ? "true" : "false"); + + do { + if (active_item_id == NULL || !iters_equal (&active_item_iter, &iter)) { + /* inactive item */ + gtk_list_store_set (widget->priv->list_store, + &iter, + CHOOSER_ITEM_IS_VISIBLE_COLUMN, should_show, + -1); + } else { + /* always show the active item */ + gtk_list_store_set (widget->priv->list_store, + &iter, + CHOOSER_ITEM_IS_VISIBLE_COLUMN, TRUE, + -1); + } + } while (gtk_tree_model_iter_next (model, &iter)); + + gtk_tree_view_set_model (GTK_TREE_VIEW (widget->priv->items_view), view_model); + g_object_unref (view_model); + + queue_update_separator_visibility (widget); + + out: + g_free (active_item_id); +} + +static void +on_shrink_animation_complete (GdmScrollableWidget *scrollable_widget, + GdmChooserWidget *widget) +{ + g_assert (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_SHRINKING); + + g_debug ("GdmChooserWidget: shrink complete"); + + widget->priv->active_row_normalized_position = 0.5; + set_inactive_items_visible (GDM_CHOOSER_WIDGET (widget), FALSE); + gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), FALSE); + widget->priv->state = GDM_CHOOSER_WIDGET_STATE_SHRUNK; + + queue_update_separator_visibility (widget); + + if (widget->priv->emit_activated_after_resize_animation) { + g_signal_emit (widget, signals[ACTIVATED], 0); + widget->priv->emit_activated_after_resize_animation = FALSE; + } +} + +static int +get_height_of_row_at_path (GdmChooserWidget *widget, + GtkTreePath *path) +{ + GdkRectangle area; + + gtk_tree_view_get_background_area (GTK_TREE_VIEW (widget->priv->items_view), + path, NULL, &area); + + return area.height; +} + +static double +get_normalized_position_of_row_at_path (GdmChooserWidget *widget, + GtkTreePath *path) +{ + GdkRectangle area_of_row_at_path; + GdkRectangle area_of_visible_rows; + GtkAllocation items_view_allocation; + + gtk_widget_get_allocation (widget->priv->items_view, &items_view_allocation); + + gtk_tree_view_get_background_area (GTK_TREE_VIEW (widget->priv->items_view), + path, NULL, &area_of_row_at_path); + + gtk_tree_view_convert_tree_to_widget_coords (GTK_TREE_VIEW (widget->priv->items_view), + area_of_visible_rows.x, + area_of_visible_rows.y, + &area_of_visible_rows.x, + &area_of_visible_rows.y); + return CLAMP (((double) area_of_row_at_path.y) / items_view_allocation.height, 0.0, 1.0); +} + +static void +start_shrink_animation (GdmChooserWidget *widget) +{ + GtkTreePath *active_row_path; + int active_row_height; + int number_of_visible_rows; + + g_assert (widget->priv->active_row != NULL); + + number_of_visible_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (widget->priv->model_sorter), NULL); + + if (number_of_visible_rows <= 1) { + on_shrink_animation_complete (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget), + widget); + return; + } + + active_row_path = get_view_path_to_active_row (widget); + active_row_height = get_height_of_row_at_path (widget, active_row_path); + widget->priv->active_row_normalized_position = get_normalized_position_of_row_at_path (widget, active_row_path); + gtk_tree_path_free (active_row_path); + + gdm_scrollable_widget_slide_to_height (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget), + active_row_height, + (GdmScrollableWidgetSlideStepFunc) + on_shrink_animation_step, widget, + (GdmScrollableWidgetSlideDoneFunc) + on_shrink_animation_complete, widget); +} + +static char * +get_first_item (GdmChooserWidget *widget) +{ + GtkTreeModel *model; + GtkTreeIter iter; + char *id; + + model = GTK_TREE_MODEL (widget->priv->list_store); + + if (!gtk_tree_model_get_iter_first (model, &iter)) { + g_assert_not_reached (); + } + + gtk_tree_model_get (model, &iter, + CHOOSER_ID_COLUMN, &id, -1); + return id; +} + +static gboolean +activate_if_one_item (GdmChooserWidget *widget) +{ + char *id; + + g_debug ("GdmChooserWidget: attempting to activate single item"); + + if (gdm_chooser_widget_get_number_of_items (widget) != 1) { + g_debug ("GdmChooserWidget: unable to activate single item - has %d items", gdm_chooser_widget_get_number_of_items (widget)); + return FALSE; + } + + id = get_first_item (widget); + if (id != NULL) { + gdm_chooser_widget_set_active_item (widget, id); + g_free (id); + } + + return FALSE; +} + +static void +_grab_focus (GtkWidget *widget) +{ + GtkWidget *foc_widget; + + foc_widget = GDM_CHOOSER_WIDGET (widget)->priv->items_view; + g_debug ("GdmChooserWidget: grabbing focus"); + + if (gtk_widget_has_focus (foc_widget)) { + g_debug ("GdmChooserWidget: not grabbing focus - already has it"); + return; + } + + gtk_widget_child_focus (foc_widget, GTK_DIR_TAB_FORWARD); +} + +static void +queue_update_visible_items (GdmChooserWidget *widget) +{ + if (widget->priv->update_items_idle_id != 0) { + g_source_remove (widget->priv->update_items_idle_id); + } + + widget->priv->update_items_idle_id = + g_timeout_add (100, (GSourceFunc) update_visible_items, widget); +} + +static void +on_grow_animation_complete (GdmScrollableWidget *scrollable_widget, + GdmChooserWidget *widget) +{ + g_assert (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWING); + widget->priv->state = GDM_CHOOSER_WIDGET_STATE_GROWN; + gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), TRUE); + queue_update_visible_items (widget); + + _grab_focus (GTK_WIDGET (widget)); +} + +static int +get_number_of_on_screen_rows (GdmChooserWidget *widget) +{ + GtkTreePath *start_path; + GtkTreePath *end_path; + int *start_index; + int *end_index; + int number_of_rows; + + if (!gtk_tree_view_get_visible_range (GTK_TREE_VIEW (widget->priv->items_view), + &start_path, &end_path)) { + return 0; + } + + start_index = gtk_tree_path_get_indices (start_path); + end_index = gtk_tree_path_get_indices (end_path); + + number_of_rows = *end_index - *start_index + 1; + + gtk_tree_path_free (start_path); + gtk_tree_path_free (end_path); + + return number_of_rows; +} + +static void +on_grow_animation_step (GdmScrollableWidget *scrollable_widget, + double progress, + int *new_height, + GdmChooserWidget *widget) +{ + int number_of_visible_rows; + int number_of_on_screen_rows; + + number_of_visible_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (widget->priv->model_sorter), NULL); + number_of_on_screen_rows = get_number_of_on_screen_rows (widget); + + GtkRequisition scrollable_widget_req; + gtk_widget_get_requisition (gtk_bin_get_child (GTK_BIN (scrollable_widget)), &scrollable_widget_req); + *new_height = scrollable_widget_req.height; +} + +static void +start_grow_animation (GdmChooserWidget *widget) +{ + GtkRequisition scrollable_widget_req; + gtk_widget_get_requisition (gtk_bin_get_child (GTK_BIN (widget->priv->scrollable_widget)), + &scrollable_widget_req); + + set_inactive_items_visible (widget, TRUE); + + gdm_scrollable_widget_slide_to_height (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget), + scrollable_widget_req.height, + (GdmScrollableWidgetSlideStepFunc) + on_grow_animation_step, widget, + (GdmScrollableWidgetSlideDoneFunc) + on_grow_animation_complete, widget); +} + +static void +skip_resize_animation (GdmChooserWidget *widget) +{ + if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_SHRINKING) { + set_inactive_items_visible (GDM_CHOOSER_WIDGET (widget), FALSE); + gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), FALSE); + widget->priv->state = GDM_CHOOSER_WIDGET_STATE_SHRUNK; + } else if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWING) { + set_inactive_items_visible (GDM_CHOOSER_WIDGET (widget), TRUE); + gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), TRUE); + widget->priv->state = GDM_CHOOSER_WIDGET_STATE_GROWN; + _grab_focus (GTK_WIDGET (widget)); + } +} + +static void +gdm_chooser_widget_grow (GdmChooserWidget *widget) +{ + if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWN) { + g_debug ("GdmChooserWidget: Asking for grow but already grown"); + return; + } + + if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_SHRINKING) { + gdm_scrollable_widget_stop_sliding (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget)); + } + + gtk_alignment_set (GTK_ALIGNMENT (widget->priv->frame_alignment), + 0.0, 0.0, 1.0, 1.0); + + set_frame_text (widget, widget->priv->inactive_text); + + widget->priv->state = GDM_CHOOSER_WIDGET_STATE_GROWING; + + if (gtk_widget_get_visible (GTK_WIDGET (widget))) { + start_grow_animation (widget); + } else { + skip_resize_animation (widget); + } +} + +static gboolean +move_cursor_to_top (GdmChooserWidget *widget) +{ + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->items_view)); + path = gtk_tree_path_new_first (); + if (gtk_tree_model_get_iter (model, &iter, path)) { + gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view), + path, + NULL, + FALSE); + + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget->priv->items_view), + path, + NULL, + TRUE, + 0.5, + 0.0); + + } + gtk_tree_path_free (path); + + widget->priv->update_cursor_idle_id = 0; + return FALSE; +} + +static gboolean +clear_selection (GdmChooserWidget *widget) +{ + GtkTreeSelection *selection; + GtkWidget *window; + + g_debug ("GdmChooserWidget: clearing selection"); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view)); + gtk_tree_selection_unselect_all (selection); + + window = gtk_widget_get_ancestor (GTK_WIDGET (widget), GTK_TYPE_WINDOW); + + if (window != NULL) { + gtk_window_set_focus (GTK_WINDOW (window), NULL); + } + + return FALSE; +} + +static void +gdm_chooser_widget_shrink (GdmChooserWidget *widget) +{ + g_assert (widget->priv->should_hide_inactive_items == TRUE); + + if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWING) { + gdm_scrollable_widget_stop_sliding (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget)); + } + + set_frame_text (widget, widget->priv->active_text); + + gtk_alignment_set (GTK_ALIGNMENT (widget->priv->frame_alignment), + 0.0, 0.0, 1.0, 0.0); + + clear_selection (widget); + + widget->priv->state = GDM_CHOOSER_WIDGET_STATE_SHRINKING; + + if (gtk_widget_get_visible (GTK_WIDGET (widget))) { + start_shrink_animation (widget); + } else { + skip_resize_animation (widget); + } +} + +static void +activate_from_row (GdmChooserWidget *widget, + GtkTreeRowReference *row) +{ + g_assert (row != NULL); + g_assert (gtk_tree_row_reference_valid (row)); + + if (widget->priv->active_row != NULL) { + gtk_tree_row_reference_free (widget->priv->active_row); + widget->priv->active_row = NULL; + } + + widget->priv->active_row = gtk_tree_row_reference_copy (row); + + if (widget->priv->should_hide_inactive_items) { + g_debug ("GdmChooserWidget: will emit activated after resize"); + widget->priv->emit_activated_after_resize_animation = TRUE; + gdm_chooser_widget_shrink (widget); + } else { + g_debug ("GdmChooserWidget: emitting activated"); + g_signal_emit (widget, signals[ACTIVATED], 0); + } +} + +static void +deactivate (GdmChooserWidget *widget) +{ + GtkTreePath *path; + + if (widget->priv->active_row == NULL) { + return; + } + + path = get_view_path_to_active_row (widget); + + gtk_tree_row_reference_free (widget->priv->active_row); + widget->priv->active_row = NULL; + + gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view), + path, NULL, FALSE); + gtk_tree_path_free (path); + + if (widget->priv->state != GDM_CHOOSER_WIDGET_STATE_GROWN) { + gdm_chooser_widget_grow (widget); + } else { + queue_update_visible_items (widget); + } + + g_signal_emit (widget, signals[DEACTIVATED], 0); +} + +void +gdm_chooser_widget_activate_selected_item (GdmChooserWidget *widget) +{ + GtkTreeRowReference *row; + gboolean is_already_active; + GtkTreePath *path; + GtkTreeModel *model; + + row = NULL; + model = GTK_TREE_MODEL (widget->priv->list_store); + is_already_active = FALSE; + + path = NULL; + + get_selected_list_path (widget, &path); + if (path == NULL) { + g_debug ("GdmChooserWidget: no row selected"); + return; + } + + if (widget->priv->active_row != NULL) { + GtkTreePath *active_path; + + active_path = gtk_tree_row_reference_get_path (widget->priv->active_row); + + if (gtk_tree_path_compare (path, active_path) == 0) { + is_already_active = TRUE; + } + gtk_tree_path_free (active_path); + } + g_assert (path != NULL); + row = gtk_tree_row_reference_new (model, path); + gtk_tree_path_free (path); + + if (!is_already_active) { + activate_from_row (widget, row); + } else { + g_debug ("GdmChooserWidget: row is already active"); + } + gtk_tree_row_reference_free (row); +} + +void +gdm_chooser_widget_set_active_item (GdmChooserWidget *widget, + const char *id) +{ + g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); + + g_debug ("GdmChooserWidget: setting active item '%s'", + id ? id : "(null)"); + + if (id != NULL) { + activate_from_item_id (widget, id); + } else { + deactivate (widget); + } +} + +static void +gdm_chooser_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmChooserWidget *self; + + self = GDM_CHOOSER_WIDGET (object); + + switch (prop_id) { + + case PROP_INACTIVE_TEXT: + g_free (self->priv->inactive_text); + self->priv->inactive_text = g_value_dup_string (value); + + if (self->priv->active_row == NULL) { + set_frame_text (self, self->priv->inactive_text); + } + break; + + case PROP_ACTIVE_TEXT: + g_free (self->priv->active_text); + self->priv->active_text = g_value_dup_string (value); + + if (self->priv->active_row != NULL) { + set_frame_text (self, self->priv->active_text); + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_chooser_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmChooserWidget *self; + + self = GDM_CHOOSER_WIDGET (object); + + switch (prop_id) { + case PROP_INACTIVE_TEXT: + g_value_set_string (value, self->priv->inactive_text); + break; + + case PROP_ACTIVE_TEXT: + g_value_set_string (value, self->priv->active_text); + break; + case PROP_LIST_VISIBLE: + g_value_set_boolean (value, self->priv->list_visible); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_chooser_widget_dispose (GObject *object) +{ + GdmChooserWidget *widget; + + widget = GDM_CHOOSER_WIDGET (object); + + if (widget->priv->update_items_idle_id > 0) { + g_source_remove (widget->priv->update_items_idle_id); + widget->priv->update_items_idle_id = 0; + } + + if (widget->priv->update_separator_idle_id > 0) { + g_source_remove (widget->priv->update_separator_idle_id); + widget->priv->update_separator_idle_id = 0; + } + + if (widget->priv->update_visibility_idle_id > 0) { + g_source_remove (widget->priv->update_visibility_idle_id); + widget->priv->update_visibility_idle_id = 0; + } + + if (widget->priv->update_cursor_idle_id > 0) { + g_source_remove (widget->priv->update_cursor_idle_id); + widget->priv->update_cursor_idle_id = 0; + } + + if (widget->priv->separator_row != NULL) { + gtk_tree_row_reference_free (widget->priv->separator_row); + widget->priv->separator_row = NULL; + } + + if (widget->priv->active_row != NULL) { + gtk_tree_row_reference_free (widget->priv->active_row); + widget->priv->active_row = NULL; + } + + if (widget->priv->inactive_text != NULL) { + g_free (widget->priv->inactive_text); + widget->priv->inactive_text = NULL; + } + + if (widget->priv->active_text != NULL) { + g_free (widget->priv->active_text); + widget->priv->active_text = NULL; + } + + if (widget->priv->in_use_message != NULL) { + g_free (widget->priv->in_use_message); + widget->priv->in_use_message = NULL; + } + + G_OBJECT_CLASS (gdm_chooser_widget_parent_class)->dispose (object); +} + +static void +gdm_chooser_widget_hide (GtkWidget *widget) +{ + skip_resize_animation (GDM_CHOOSER_WIDGET (widget)); + GTK_WIDGET_CLASS (gdm_chooser_widget_parent_class)->hide (widget); +} + +static void +gdm_chooser_widget_show (GtkWidget *widget) +{ + skip_resize_animation (GDM_CHOOSER_WIDGET (widget)); + + GTK_WIDGET_CLASS (gdm_chooser_widget_parent_class)->show (widget); +} + +static void +gdm_chooser_widget_map (GtkWidget *widget) +{ + queue_update_visible_items (GDM_CHOOSER_WIDGET (widget)); + + GTK_WIDGET_CLASS (gdm_chooser_widget_parent_class)->map (widget); +} + +static void +gdm_chooser_widget_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GdmChooserWidget *chooser_widget; + + GTK_WIDGET_CLASS (gdm_chooser_widget_parent_class)->size_allocate (widget, allocation); + + chooser_widget = GDM_CHOOSER_WIDGET (widget); + +} + +static gboolean +gdm_chooser_widget_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + /* Since we only have one focusable child (the tree view), + * no matter which direction we're going the rules are the + * same: + * + * 1) if it's aready got focus, return FALSE to surrender + * that focus. + * 2) if it doesn't already have focus, then grab it + */ + if (gtk_container_get_focus_child (GTK_CONTAINER (widget)) != NULL) { + g_debug ("GdmChooserWidget: not focusing - focus child not null"); + return FALSE; + } + + _grab_focus (widget); + + return TRUE; +} + +static gboolean +gdm_chooser_widget_focus_in_event (GtkWidget *widget, + GdkEventFocus *focus_event) +{ + /* We don't ever want the chooser widget itself to have focus. + * Focus should always go to the tree view. + */ + _grab_focus (widget); + + return FALSE; +} + +static void +gdm_chooser_widget_class_init (GdmChooserWidgetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = gdm_chooser_widget_get_property; + object_class->set_property = gdm_chooser_widget_set_property; + object_class->dispose = gdm_chooser_widget_dispose; + object_class->finalize = gdm_chooser_widget_finalize; + widget_class->size_allocate = gdm_chooser_widget_size_allocate; + widget_class->hide = gdm_chooser_widget_hide; + widget_class->show = gdm_chooser_widget_show; + widget_class->map = gdm_chooser_widget_map; + widget_class->focus = gdm_chooser_widget_focus; + widget_class->focus_in_event = gdm_chooser_widget_focus_in_event; + + signals [LOADED] = g_signal_new ("loaded", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmChooserWidgetClass, loaded), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + signals [ACTIVATED] = g_signal_new ("activated", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmChooserWidgetClass, activated), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + signals [DEACTIVATED] = g_signal_new ("deactivated", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmChooserWidgetClass, deactivated), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + g_object_class_install_property (object_class, + PROP_INACTIVE_TEXT, + g_param_spec_string ("inactive-text", + _("Inactive Text"), + _("The text to use in the label if the " + "user hasn't picked an item yet"), + NULL, + (G_PARAM_READWRITE | + G_PARAM_CONSTRUCT))); + g_object_class_install_property (object_class, + PROP_ACTIVE_TEXT, + g_param_spec_string ("active-text", + _("Active Text"), + _("The text to use in the label if the " + "user has picked an item"), + NULL, + (G_PARAM_READWRITE | + G_PARAM_CONSTRUCT))); + + g_object_class_install_property (object_class, + PROP_LIST_VISIBLE, + g_param_spec_boolean ("list-visible", + _("List Visible"), + _("Whether the chooser list is visible"), + FALSE, + G_PARAM_READABLE)); + + g_type_class_add_private (klass, sizeof (GdmChooserWidgetPrivate)); +} + +static void +on_row_activated (GtkTreeView *tree_view, + GtkTreePath *tree_path, + GtkTreeViewColumn *tree_column, + GdmChooserWidget *widget) +{ + char *path_str; + + path_str = gtk_tree_path_to_string (tree_path); + g_debug ("GdmChooserWidget: row activated '%s'", path_str ? path_str : "(null)"); + g_free (path_str); + gdm_chooser_widget_activate_selected_item (widget); +} + +static gboolean +path_is_separator (GdmChooserWidget *widget, + GtkTreeModel *model, + GtkTreePath *path) +{ + GtkTreePath *separator_path; + GtkTreePath *translated_path; + gboolean is_separator; + + separator_path = gtk_tree_row_reference_get_path (widget->priv->separator_row); + + if (separator_path == NULL) { + return FALSE; + } + + if (model == GTK_TREE_MODEL (widget->priv->model_sorter)) { + GtkTreePath *filtered_path; + + filtered_path = gtk_tree_model_sort_convert_path_to_child_path (widget->priv->model_sorter, path); + + translated_path = gtk_tree_model_filter_convert_path_to_child_path (widget->priv->model_filter, filtered_path); + gtk_tree_path_free (filtered_path); + } else if (model == GTK_TREE_MODEL (widget->priv->model_filter)) { + translated_path = gtk_tree_model_filter_convert_path_to_child_path (widget->priv->model_filter, path); + } else { + g_assert (model == GTK_TREE_MODEL (widget->priv->list_store)); + translated_path = gtk_tree_path_copy (path); + } + + if (gtk_tree_path_compare (separator_path, translated_path) == 0) { + is_separator = TRUE; + } else { + is_separator = FALSE; + } + gtk_tree_path_free (translated_path); + + return is_separator; +} + +static int +gdm_multilingual_collate (const gchar *text_a, const gchar *text_b) +{ + PangoDirection direction_a; + PangoDirection direction_b; + const gchar *p_a; + const gchar *p_b; + gchar *org_locale = NULL; + gchar *sub_a; + gchar *sub_b; + gunichar ch_a; + gunichar ch_b; + GUnicodeScript script_a; + GUnicodeScript script_b; + gboolean composed_alpha_a; + gboolean composed_alpha_b; + gboolean all_alpha_a; + gboolean all_alpha_b; + int result; + + direction_a = pango_find_base_dir (text_a, -1); + direction_b = pango_find_base_dir (text_b, -1); + if ((direction_a == PANGO_DIRECTION_LTR || + direction_a == PANGO_DIRECTION_NEUTRAL) && + direction_b == PANGO_DIRECTION_RTL) + return -1; + if (direction_a == PANGO_DIRECTION_RTL && + (direction_b == PANGO_DIRECTION_LTR || + direction_b == PANGO_DIRECTION_NEUTRAL)) + return 1; + + if (!g_get_charset (NULL)) { + org_locale = g_strdup (setlocale (LC_ALL, NULL)); + if (!setlocale (LC_ALL, "en_US.UTF-8")) { + setlocale (LC_ALL, org_locale); + g_free (org_locale); + org_locale = NULL; + } + } + + result = 0; + all_alpha_a = all_alpha_b = TRUE; + for (p_a = text_a, p_b = text_b; + p_a && *p_a && p_b && *p_b; + p_a = g_utf8_next_char (p_a), p_b = g_utf8_next_char (p_b)) { + ch_a = g_utf8_get_char (p_a); + ch_b = g_utf8_get_char (p_b); + script_a = g_unichar_get_script (ch_a); + script_b = g_unichar_get_script (ch_b); + composed_alpha_a = (script_a == G_UNICODE_SCRIPT_LATIN || + script_a == G_UNICODE_SCRIPT_GREEK || + script_a == G_UNICODE_SCRIPT_CYRILLIC ); + composed_alpha_b = (script_b == G_UNICODE_SCRIPT_LATIN || + script_b == G_UNICODE_SCRIPT_GREEK || + script_b == G_UNICODE_SCRIPT_CYRILLIC ); + all_alpha_a &= composed_alpha_a; + all_alpha_b &= composed_alpha_b; + if (all_alpha_a && !composed_alpha_b && + ch_b >= 0x530) { + result = -1; + break; + } else if (!composed_alpha_a && all_alpha_b && + ch_a >= 0x530) { + result = 1; + break; + } else if (ch_a != ch_b) { + sub_a = g_strndup (text_a, g_utf8_next_char (p_a) - text_a); + sub_b = g_strndup (text_b, g_utf8_next_char (p_b) - text_b); + result = g_utf8_collate (sub_a, sub_b); + g_free (sub_a); + g_free (sub_b); + if (result != 0) { + break; + } + } + } + if (result != 0) { + if (org_locale) { + setlocale (LC_ALL, org_locale); + g_free (org_locale); + org_locale = NULL; + } + return result; + } + if (org_locale) { + setlocale (LC_ALL, org_locale); + g_free (org_locale); + org_locale = NULL; + } + return g_utf8_collate (text_a, text_b); +} + +static int +compare_item (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer data) +{ + GdmChooserWidget *widget; + char *name_a; + char *name_b; + gulong prio_a; + gulong prio_b; + gboolean is_separate_a; + gboolean is_separate_b; + int result; + int direction; + GtkTreeIter *separator_iter; + char *id; + PangoAttrList *attrs; + + g_assert (GDM_IS_CHOOSER_WIDGET (data)); + + widget = GDM_CHOOSER_WIDGET (data); + + separator_iter = NULL; + if (widget->priv->separator_row != NULL) { + + GtkTreePath *path_a; + GtkTreePath *path_b; + + path_a = gtk_tree_model_get_path (model, a); + path_b = gtk_tree_model_get_path (model, b); + + if (path_is_separator (widget, model, path_a)) { + separator_iter = a; + } else if (path_is_separator (widget, model, path_b)) { + separator_iter = b; + } + + gtk_tree_path_free (path_a); + gtk_tree_path_free (path_b); + } + + name_a = NULL; + is_separate_a = FALSE; + if (separator_iter != a) { + gtk_tree_model_get (model, a, + CHOOSER_NAME_COLUMN, &name_a, + CHOOSER_PRIORITY_COLUMN, &prio_a, + CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate_a, + -1); + } + + name_b = NULL; + is_separate_b = FALSE; + if (separator_iter != b) { + gtk_tree_model_get (model, b, + CHOOSER_NAME_COLUMN, &name_b, + CHOOSER_ID_COLUMN, &id, + CHOOSER_PRIORITY_COLUMN, &prio_b, + CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate_b, + -1); + } + + if (widget->priv->separator_position == GDM_CHOOSER_WIDGET_POSITION_TOP) { + direction = -1; + } else { + direction = 1; + } + + if (separator_iter == b) { + result = is_separate_a? 1 : -1; + result *= direction; + } else if (separator_iter == a) { + result = is_separate_b? -1 : 1; + result *= direction; + } else if (is_separate_b == is_separate_a) { + if (prio_a == prio_b) { + char *text_a; + char *text_b; + + text_a = NULL; + text_b = NULL; + pango_parse_markup (name_a, -1, 0, &attrs, &text_a, NULL, NULL); + if (text_a == NULL) { + g_debug ("GdmChooserWidget: unable to parse markup: '%s'", name_a); + } + pango_parse_markup (name_b, -1, 0, &attrs, &text_b, NULL, NULL); + if (text_b == NULL) { + g_debug ("GdmChooserWidget: unable to parse markup: '%s'", name_b); + } + if (text_a != NULL && text_b != NULL) { + result = gdm_multilingual_collate (text_a, text_b); + } else { + result = gdm_multilingual_collate (name_a, name_b); + } + g_free (text_a); + g_free (text_b); + } else if (prio_a > prio_b) { + result = -1; + } else { + result = 1; + } + } else { + result = is_separate_a - is_separate_b; + result *= direction; + } + + g_free (name_a); + g_free (name_b); + + return result; +} + +static void +name_cell_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + GdmChooserWidget *widget) +{ + gboolean is_in_use; + char *name; + char *markup; + + name = NULL; + gtk_tree_model_get (model, + iter, + CHOOSER_ITEM_IS_IN_USE_COLUMN, &is_in_use, + CHOOSER_NAME_COLUMN, &name, + -1); + + if (is_in_use) { + markup = g_strdup_printf ("<b>%s</b>\n" + "<i><span size=\"x-small\">%s</span></i>", + name ? name : "(null)", widget->priv->in_use_message); + } else { + markup = g_strdup_printf ("%s", name ? name : "(null)"); + } + g_free (name); + + g_object_set (cell, "markup", markup, NULL); + g_free (markup); +} + +static void +check_cell_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + GdmChooserWidget *widget) +{ + gboolean is_in_use; + GdkPixbuf *pixbuf; + + gtk_tree_model_get (model, + iter, + CHOOSER_ITEM_IS_IN_USE_COLUMN, &is_in_use, + -1); + + if (is_in_use) { + pixbuf = widget->priv->is_in_use_pixbuf; + } else { + pixbuf = NULL; + } + + g_object_set (cell, "pixbuf", pixbuf, NULL); +} + +static GdkPixbuf * +get_is_in_use_pixbuf (GdmChooserWidget *widget) +{ + GtkIconTheme *theme; + GdkPixbuf *pixbuf; + + theme = gtk_icon_theme_get_default (); + pixbuf = gtk_icon_theme_load_icon (theme, + "emblem-default", + GDM_CHOOSER_WIDGET_DEFAULT_ICON_SIZE / 3, + 0, + NULL); + + return pixbuf; +} + +static gboolean +separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + GdmChooserWidget *widget; + GtkTreePath *path; + gboolean is_separator; + + g_assert (GDM_IS_CHOOSER_WIDGET (data)); + + widget = GDM_CHOOSER_WIDGET (data); + + g_assert (widget->priv->separator_row != NULL); + + path = gtk_tree_model_get_path (model, iter); + + is_separator = path_is_separator (widget, model, path); + + gtk_tree_path_free (path); + + return is_separator; +} + +static void +add_separator (GdmChooserWidget *widget) +{ + GtkTreeIter iter; + GtkTreeModel *model; + GtkTreePath *path; + + g_assert (widget->priv->separator_row == NULL); + + model = GTK_TREE_MODEL (widget->priv->list_store); + + gtk_list_store_insert_with_values (widget->priv->list_store, + &iter, 0, + CHOOSER_ID_COLUMN, "-", -1); + path = gtk_tree_model_get_path (model, &iter); + widget->priv->separator_row = gtk_tree_row_reference_new (model, path); + gtk_tree_path_free (path); +} + +static gboolean +update_column_visibility (GdmChooserWidget *widget) +{ + g_debug ("GdmChooserWidget: updating column visibility"); + + if (widget->priv->number_of_rows_with_images > 0) { + gtk_tree_view_column_set_visible (widget->priv->image_column, + TRUE); + } else { + gtk_tree_view_column_set_visible (widget->priv->image_column, + FALSE); + } + if (widget->priv->number_of_rows_with_status > 0) { + gtk_tree_view_column_set_visible (widget->priv->status_column, + TRUE); + } else { + gtk_tree_view_column_set_visible (widget->priv->status_column, + FALSE); + } + + return FALSE; +} + +static void +clear_canceled_visibility_update (GdmChooserWidget *widget) +{ + widget->priv->update_idle_id = 0; +} + +static void +queue_column_visibility_update (GdmChooserWidget *widget) +{ + if (widget->priv->update_idle_id == 0) { + widget->priv->update_idle_id = + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc) + update_column_visibility, widget, + (GDestroyNotify) + clear_canceled_visibility_update); + } +} + +static void +on_row_changed (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + GdmChooserWidget *widget) +{ + queue_column_visibility_update (widget); +} + +static void +add_frame (GdmChooserWidget *widget) +{ + widget->priv->frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (widget->priv->frame), + GTK_SHADOW_NONE); + gtk_widget_show (widget->priv->frame); + gtk_container_add (GTK_CONTAINER (widget), widget->priv->frame); + + widget->priv->frame_alignment = gtk_alignment_new (0.0, 0.0, 1.0, 1.0); + gtk_widget_show (widget->priv->frame_alignment); + gtk_container_add (GTK_CONTAINER (widget->priv->frame), + widget->priv->frame_alignment); +} + +static gboolean +on_button_release (GtkTreeView *items_view, + GdkEventButton *event, + GdmChooserWidget *widget) +{ + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreeSelection *selection; + + if (!widget->priv->should_hide_inactive_items) { + return FALSE; + } + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view)); + if (gtk_tree_selection_get_selected (selection, &model, &iter)) { + GtkTreePath *path; + + path = gtk_tree_model_get_path (model, &iter); + gtk_tree_view_row_activated (GTK_TREE_VIEW (items_view), + path, NULL); + gtk_tree_path_free (path); + } + + return FALSE; +} + +static gboolean +search_equal_func (GtkTreeModel *model, + int column, + const char *key, + GtkTreeIter *iter, + GdmChooserWidget *widget) +{ + char *id; + char *name; + char *key_folded; + gboolean ret; + + if (key == NULL) { + return FALSE; + } + + ret = TRUE; + id = NULL; + name = NULL; + + key_folded = g_utf8_casefold (key, -1); + + gtk_tree_model_get (model, + iter, + CHOOSER_ID_COLUMN, &id, + CHOOSER_NAME_COLUMN, &name, + -1); + if (name != NULL) { + char *name_folded; + + name_folded = g_utf8_casefold (name, -1); + ret = !g_str_has_prefix (name_folded, key_folded); + g_free (name_folded); + + if (!ret) { + goto out; + } + } + + if (id != NULL) { + char *id_folded; + + + id_folded = g_utf8_casefold (id, -1); + ret = !g_str_has_prefix (id_folded, key_folded); + g_free (id_folded); + + if (!ret) { + goto out; + } + } + out: + g_free (id); + g_free (name); + g_free (key_folded); + + return ret; +} + +static void +search_position_func (GtkTreeView *tree_view, + GtkWidget *search_dialog, + gpointer user_data) +{ + /* Move it outside the region viewable by + * the user. + * FIXME: This is pretty inelegant. + * + * It might be nicer to make a GdmOffscreenBin + * widget that we pack into the chooser widget below + * the frame but gets redirected offscreen. + * + * Then we would add a GtkEntry to the bin and set + * that entry as the search entry for the tree view + * instead of using a search position func. + */ + gtk_window_move (GTK_WINDOW (search_dialog), -24000, -24000); +} + +static void +on_selection_changed (GtkTreeSelection *selection, + GdmChooserWidget *widget) +{ + GtkTreePath *path; + + get_selected_list_path (widget, &path); + if (path != NULL) { + char *path_str; + path_str = gtk_tree_path_to_string (path); + g_debug ("GdmChooserWidget: selection change to list path '%s'", path_str); + g_free (path_str); + } else { + g_debug ("GdmChooserWidget: selection cleared"); + } +} + +static void +on_adjustment_value_changed (GtkAdjustment *adjustment, + GdmChooserWidget *widget) +{ + queue_update_visible_items (widget); +} + +static void +gdm_chooser_widget_init (GdmChooserWidget *widget) +{ + GtkTreeViewColumn *column; + GtkTreeSelection *selection; + GtkCellRenderer *renderer; + GtkAdjustment *adjustment; + + widget->priv = GDM_CHOOSER_WIDGET_GET_PRIVATE (widget); + + /* Even though, we're a container and also don't ever take + * focus for ourselve, we set CAN_FOCUS so that gtk_widget_grab_focus + * works on us. We then override grab_focus requests to + * be redirected our internal tree view + */ + gtk_widget_set_can_focus (GTK_WIDGET (widget), TRUE); + + gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 0, 0, 0); + + add_frame (widget); + + widget->priv->scrollable_widget = gdm_scrollable_widget_new (); + gtk_widget_show (widget->priv->scrollable_widget); + gtk_container_add (GTK_CONTAINER (widget->priv->frame_alignment), + widget->priv->scrollable_widget); + + widget->priv->items_view = gtk_tree_view_new (); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (widget->priv->items_view), + FALSE); + g_signal_connect (widget->priv->items_view, + "row-activated", + G_CALLBACK (on_row_activated), + widget); + + gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (widget->priv->items_view), + (GtkTreeViewSearchEqualFunc)search_equal_func, + widget, + NULL); + + gtk_tree_view_set_search_position_func (GTK_TREE_VIEW (widget->priv->items_view), + (GtkTreeViewSearchPositionFunc)search_position_func, + widget, + NULL); + + /* hack to make single-click activate work + */ + g_signal_connect_after (widget->priv->items_view, + "button-release-event", + G_CALLBACK (on_button_release), + widget); + + gtk_widget_show (widget->priv->items_view); + gtk_container_add (GTK_CONTAINER (widget->priv->scrollable_widget), + widget->priv->items_view); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); + + g_signal_connect (selection, "changed", G_CALLBACK (on_selection_changed), widget); + + g_assert (NUMBER_OF_CHOOSER_COLUMNS == 13); + widget->priv->list_store = gtk_list_store_new (NUMBER_OF_CHOOSER_COLUMNS, + GDK_TYPE_PIXBUF, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_ULONG, + G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_STRING, + G_TYPE_POINTER, + G_TYPE_POINTER); + + widget->priv->model_filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (widget->priv->list_store), NULL)); + + gtk_tree_model_filter_set_visible_column (widget->priv->model_filter, + CHOOSER_ITEM_IS_VISIBLE_COLUMN); + g_signal_connect (G_OBJECT (widget->priv->model_filter), "row-changed", + G_CALLBACK (on_row_changed), widget); + + widget->priv->model_sorter = GTK_TREE_MODEL_SORT (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (widget->priv->model_filter))); + + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (widget->priv->model_sorter), + CHOOSER_ID_COLUMN, + compare_item, + widget, NULL); + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (widget->priv->model_sorter), + CHOOSER_ID_COLUMN, + GTK_SORT_ASCENDING); + gtk_tree_view_set_model (GTK_TREE_VIEW (widget->priv->items_view), + GTK_TREE_MODEL (widget->priv->model_sorter)); + gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (widget->priv->items_view), + separator_func, + widget, NULL); + + /* IMAGE COLUMN */ + renderer = gtk_cell_renderer_pixbuf_new (); + column = gtk_tree_view_column_new (); + gtk_tree_view_column_pack_start (column, renderer, FALSE); + gtk_tree_view_append_column (GTK_TREE_VIEW (widget->priv->items_view), column); + widget->priv->image_column = column; + + gtk_tree_view_column_set_attributes (column, + renderer, + "pixbuf", CHOOSER_IMAGE_COLUMN, + NULL); + + g_object_set (renderer, + "xalign", 1.0, + NULL); + + /* NAME COLUMN */ + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new (); + gtk_tree_view_column_pack_start (column, renderer, FALSE); + gtk_tree_view_append_column (GTK_TREE_VIEW (widget->priv->items_view), column); + gtk_tree_view_column_set_cell_data_func (column, + renderer, + (GtkTreeCellDataFunc) name_cell_data_func, + widget, + NULL); + + gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (widget->priv->items_view), + CHOOSER_COMMENT_COLUMN); + + /* STATUS COLUMN */ + renderer = gtk_cell_renderer_pixbuf_new (); + column = gtk_tree_view_column_new (); + gtk_tree_view_column_pack_start (column, renderer, FALSE); + gtk_tree_view_append_column (GTK_TREE_VIEW (widget->priv->items_view), column); + widget->priv->status_column = column; + + gtk_tree_view_column_set_cell_data_func (column, + renderer, + (GtkTreeCellDataFunc) check_cell_data_func, + widget, + NULL); + widget->priv->is_in_use_pixbuf = get_is_in_use_pixbuf (widget); + + renderer = gdm_cell_renderer_timer_new (); + gtk_tree_view_column_pack_start (column, renderer, FALSE); + gtk_tree_view_column_add_attribute (column, renderer, "value", + CHOOSER_TIMER_VALUE_COLUMN); + + widget->priv->rows_with_timers = + g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) + gtk_tree_row_reference_free); + + add_separator (widget); + queue_column_visibility_update (widget); + + adjustment = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (widget->priv->items_view)); + g_signal_connect (adjustment, "value-changed", G_CALLBACK (on_adjustment_value_changed), widget); +} + +static void +gdm_chooser_widget_finalize (GObject *object) +{ + GdmChooserWidget *widget; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_CHOOSER_WIDGET (object)); + + widget = GDM_CHOOSER_WIDGET (object); + + g_return_if_fail (widget->priv != NULL); + + g_hash_table_destroy (widget->priv->rows_with_timers); + widget->priv->rows_with_timers = NULL; + + G_OBJECT_CLASS (gdm_chooser_widget_parent_class)->finalize (object); +} + +GtkWidget * +gdm_chooser_widget_new (const char *inactive_text, + const char *active_text) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_CHOOSER_WIDGET, + "inactive-text", inactive_text, + "active-text", active_text, NULL); + + return GTK_WIDGET (object); +} + +void +gdm_chooser_widget_update_item (GdmChooserWidget *widget, + const char *id, + GdkPixbuf *new_image, + const char *new_name, + const char *new_comment, + gulong new_priority, + gboolean new_in_use, + gboolean new_is_separate) +{ + GtkTreeModel *model; + GtkTreeIter iter; + GdkPixbuf *image; + gboolean is_separate; + gboolean in_use; + + g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); + + model = GTK_TREE_MODEL (widget->priv->list_store); + + if (!find_item (widget, id, &iter)) { + g_critical ("Tried to remove non-existing item from chooser"); + return; + } + + is_separate = FALSE; + gtk_tree_model_get (model, &iter, + CHOOSER_IMAGE_COLUMN, &image, + CHOOSER_ITEM_IS_IN_USE_COLUMN, &in_use, + CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate, + -1); + + if (image != new_image) { + if (image == NULL && new_image != NULL) { + widget->priv->number_of_rows_with_images++; + } else if (image != NULL && new_image == NULL) { + widget->priv->number_of_rows_with_images--; + } + queue_column_visibility_update (widget); + } + if (image != NULL) { + g_object_unref (image); + } + + if (in_use != new_in_use) { + if (new_in_use) { + widget->priv->number_of_rows_with_status++; + } else { + widget->priv->number_of_rows_with_status--; + } + queue_column_visibility_update (widget); + } + + if (is_separate != new_is_separate) { + if (new_is_separate) { + widget->priv->number_of_separated_rows++; + widget->priv->number_of_normal_rows--; + } else { + widget->priv->number_of_separated_rows--; + widget->priv->number_of_normal_rows++; + } + queue_update_separator_visibility (widget); + } + + gtk_list_store_set (widget->priv->list_store, + &iter, + CHOOSER_IMAGE_COLUMN, new_image, + CHOOSER_NAME_COLUMN, new_name, + CHOOSER_COMMENT_COLUMN, new_comment, + CHOOSER_PRIORITY_COLUMN, new_priority, + CHOOSER_ITEM_IS_IN_USE_COLUMN, new_in_use, + CHOOSER_ITEM_IS_SEPARATED_COLUMN, new_is_separate, + -1); +} + +static void +queue_update_chooser_visibility (GdmChooserWidget *widget) +{ + if (widget->priv->update_visibility_idle_id == 0) { + widget->priv->update_visibility_idle_id = + g_idle_add ((GSourceFunc) update_chooser_visibility, widget); + } +} + +static void +queue_move_cursor_to_top (GdmChooserWidget *widget) +{ + if (widget->priv->update_cursor_idle_id == 0) { + widget->priv->update_cursor_idle_id = + g_idle_add ((GSourceFunc) move_cursor_to_top, widget); + } +} + +void +gdm_chooser_widget_add_item (GdmChooserWidget *widget, + const char *id, + GdkPixbuf *image, + const char *name, + const char *comment, + gulong priority, + gboolean in_use, + gboolean keep_separate, + GdmChooserWidgetItemLoadFunc load_func, + gpointer load_data) +{ + gboolean is_visible; + + g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); + + if (keep_separate) { + widget->priv->number_of_separated_rows++; + } else { + widget->priv->number_of_normal_rows++; + } + queue_update_separator_visibility (widget); + + if (in_use) { + widget->priv->number_of_rows_with_status++; + queue_column_visibility_update (widget); + } + + if (image != NULL) { + widget->priv->number_of_rows_with_images++; + queue_column_visibility_update (widget); + } + + is_visible = widget->priv->active_row == NULL; + + gtk_list_store_insert_with_values (widget->priv->list_store, + NULL, 0, + CHOOSER_IMAGE_COLUMN, image, + CHOOSER_NAME_COLUMN, name, + CHOOSER_COMMENT_COLUMN, comment, + CHOOSER_PRIORITY_COLUMN, priority, + CHOOSER_ITEM_IS_IN_USE_COLUMN, in_use, + CHOOSER_ITEM_IS_SEPARATED_COLUMN, keep_separate, + CHOOSER_ITEM_IS_VISIBLE_COLUMN, is_visible, + CHOOSER_ID_COLUMN, id, + CHOOSER_LOAD_FUNC_COLUMN, load_func, + CHOOSER_LOAD_DATA_COLUMN, load_data, + -1); + + queue_update_chooser_visibility (widget); +} + +void +gdm_chooser_widget_remove_item (GdmChooserWidget *widget, + const char *id) +{ + GtkTreeModel *model; + GtkTreeIter iter; + GdkPixbuf *image; + gboolean is_separate; + gboolean is_in_use; + + g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); + + model = GTK_TREE_MODEL (widget->priv->list_store); + + if (!find_item (widget, id, &iter)) { + g_critical ("Tried to remove non-existing item from chooser"); + return; + } + + is_separate = FALSE; + gtk_tree_model_get (model, &iter, + CHOOSER_IMAGE_COLUMN, &image, + CHOOSER_ITEM_IS_IN_USE_COLUMN, &is_in_use, + CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate, + -1); + + if (image != NULL) { + widget->priv->number_of_rows_with_images--; + g_object_unref (image); + } + + if (is_in_use) { + widget->priv->number_of_rows_with_status--; + queue_column_visibility_update (widget); + } + + if (is_separate) { + widget->priv->number_of_separated_rows--; + } else { + widget->priv->number_of_normal_rows--; + } + queue_update_separator_visibility (widget); + + gtk_list_store_remove (widget->priv->list_store, &iter); + + queue_update_chooser_visibility (widget); +} + +gboolean +gdm_chooser_widget_lookup_item (GdmChooserWidget *widget, + const char *id, + GdkPixbuf **image, + char **name, + char **comment, + gulong *priority, + gboolean *is_in_use, + gboolean *is_separate) +{ + GtkTreeIter iter; + char *active_item_id; + + g_return_val_if_fail (GDM_IS_CHOOSER_WIDGET (widget), FALSE); + g_return_val_if_fail (id != NULL, FALSE); + + active_item_id = get_active_item_id (widget, &iter); + + if (active_item_id == NULL || strcmp (active_item_id, id) != 0) { + g_free (active_item_id); + active_item_id = NULL; + + if (!find_item (widget, id, &iter)) { + return FALSE; + } + } + g_free (active_item_id); + + if (image != NULL) { + gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, + CHOOSER_IMAGE_COLUMN, image, -1); + } + + if (name != NULL) { + gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, + CHOOSER_NAME_COLUMN, name, -1); + } + + if (priority != NULL) { + gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, + CHOOSER_PRIORITY_COLUMN, priority, -1); + } + + if (is_in_use != NULL) { + gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, + CHOOSER_ITEM_IS_IN_USE_COLUMN, is_in_use, -1); + } + + if (is_separate != NULL) { + gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, + CHOOSER_ITEM_IS_SEPARATED_COLUMN, is_separate, -1); + } + + return TRUE; +} + +void +gdm_chooser_widget_set_item_in_use (GdmChooserWidget *widget, + const char *id, + gboolean is_in_use) +{ + GtkTreeIter iter; + gboolean was_in_use; + + g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); + + if (!find_item (widget, id, &iter)) { + return; + } + + gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, + CHOOSER_ITEM_IS_IN_USE_COLUMN, &was_in_use, + -1); + + if (was_in_use != is_in_use) { + + if (is_in_use) { + widget->priv->number_of_rows_with_status++; + } else { + widget->priv->number_of_rows_with_status--; + } + queue_column_visibility_update (widget); + + gtk_list_store_set (widget->priv->list_store, + &iter, + CHOOSER_ITEM_IS_IN_USE_COLUMN, is_in_use, + -1); + + } +} + +void +gdm_chooser_widget_set_item_priority (GdmChooserWidget *widget, + const char *id, + gulong priority) +{ + GtkTreeIter iter; + gulong was_priority; + + g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); + + if (!find_item (widget, id, &iter)) { + return; + } + + gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, + CHOOSER_PRIORITY_COLUMN, &was_priority, + -1); + + if (was_priority != priority) { + + gtk_list_store_set (widget->priv->list_store, + &iter, + CHOOSER_PRIORITY_COLUMN, priority, + -1); + gtk_tree_model_filter_refilter (widget->priv->model_filter); + } +} + +static double +get_current_time (void) +{ + const double microseconds_per_second = 1000000.0; + double timestamp; + GTimeVal now; + + g_get_current_time (&now); + + timestamp = ((microseconds_per_second * now.tv_sec) + now.tv_usec) / + microseconds_per_second; + + return timestamp; +} + +static gboolean +on_timer_timeout (GdmChooserWidget *widget) +{ + GHashTableIter iter; + GSList *list; + GSList *tmp; + gpointer key; + gpointer value; + double now; + + list = NULL; + g_hash_table_iter_init (&iter, widget->priv->rows_with_timers); + while (g_hash_table_iter_next (&iter, &key, &value)) { + list = g_slist_prepend (list, value); + } + + now = get_current_time (); + for (tmp = list; tmp != NULL; tmp = tmp->next) { + GtkTreeRowReference *row; + + row = (GtkTreeRowReference *) tmp->data; + + update_timer_from_time (widget, row, now); + } + g_slist_free (list); + + return TRUE; +} + +static void +start_timer (GdmChooserWidget *widget, + GtkTreeRowReference *row, + double duration) +{ + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + + model = GTK_TREE_MODEL (widget->priv->list_store); + + path = gtk_tree_row_reference_get_path (row); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + + gtk_list_store_set (widget->priv->list_store, &iter, + CHOOSER_TIMER_START_TIME_COLUMN, + get_current_time (), -1); + gtk_list_store_set (widget->priv->list_store, &iter, + CHOOSER_TIMER_DURATION_COLUMN, + duration, -1); + gtk_list_store_set (widget->priv->list_store, &iter, + CHOOSER_TIMER_VALUE_COLUMN, 0.0, -1); + + widget->priv->number_of_active_timers++; + if (widget->priv->timer_animation_timeout_id == 0) { + g_assert (g_hash_table_size (widget->priv->rows_with_timers) == 1); + + widget->priv->timer_animation_timeout_id = + g_timeout_add (1000 / 20, + (GSourceFunc) on_timer_timeout, + widget); + + } +} + +static void +stop_timer (GdmChooserWidget *widget, + GtkTreeRowReference *row) +{ + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + + model = GTK_TREE_MODEL (widget->priv->list_store); + + path = gtk_tree_row_reference_get_path (row); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + + gtk_list_store_set (widget->priv->list_store, &iter, + CHOOSER_TIMER_START_TIME_COLUMN, + 0.0, -1); + gtk_list_store_set (widget->priv->list_store, &iter, + CHOOSER_TIMER_DURATION_COLUMN, + 0.0, -1); + gtk_list_store_set (widget->priv->list_store, &iter, + CHOOSER_TIMER_VALUE_COLUMN, 0.0, -1); + + widget->priv->number_of_active_timers--; + if (widget->priv->number_of_active_timers == 0) { + g_source_remove (widget->priv->timer_animation_timeout_id); + widget->priv->timer_animation_timeout_id = 0; + } +} + +static void +update_timer_from_time (GdmChooserWidget *widget, + GtkTreeRowReference *row, + double now) +{ + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + double start_time; + double duration; + double elapsed_ratio; + + model = GTK_TREE_MODEL (widget->priv->list_store); + + path = gtk_tree_row_reference_get_path (row); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + + gtk_tree_model_get (model, &iter, + CHOOSER_TIMER_START_TIME_COLUMN, &start_time, + CHOOSER_TIMER_DURATION_COLUMN, &duration, -1); + + if (duration > G_MINDOUBLE) { + elapsed_ratio = (now - start_time) / duration; + } else { + elapsed_ratio = 0.0; + } + + gtk_list_store_set (widget->priv->list_store, &iter, + CHOOSER_TIMER_VALUE_COLUMN, + elapsed_ratio, -1); + + if (elapsed_ratio > .999) { + char *id; + + stop_timer (widget, row); + + gtk_tree_model_get (model, &iter, + CHOOSER_ID_COLUMN, &id, -1); + g_hash_table_remove (widget->priv->rows_with_timers, + id); + g_free (id); + + widget->priv->number_of_rows_with_status--; + queue_column_visibility_update (widget); + } +} + +void +gdm_chooser_widget_set_item_timer (GdmChooserWidget *widget, + const char *id, + gulong timeout) +{ + GtkTreeModel *model; + GtkTreeRowReference *row; + + g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); + + model = GTK_TREE_MODEL (widget->priv->list_store); + + row = g_hash_table_lookup (widget->priv->rows_with_timers, + id); + + g_assert (row == NULL || gtk_tree_row_reference_valid (row)); + + if (row != NULL) { + stop_timer (widget, row); + } + + if (timeout == 0) { + if (row == NULL) { + g_warning ("could not find item with ID '%s' to " + "remove timer", id); + return; + } + + g_hash_table_remove (widget->priv->rows_with_timers, + id); + gtk_tree_model_filter_refilter (widget->priv->model_filter); + return; + } + + if (row == NULL) { + GtkTreeIter iter; + GtkTreePath *path; + + if (!find_item (widget, id, &iter)) { + g_warning ("could not find item with ID '%s' to " + "add timer", id); + return; + } + + path = gtk_tree_model_get_path (model, &iter); + row = gtk_tree_row_reference_new (model, path); + + g_hash_table_insert (widget->priv->rows_with_timers, + g_strdup (id), row); + + widget->priv->number_of_rows_with_status++; + queue_column_visibility_update (widget); + } + + start_timer (widget, row, timeout / 1000.0); +} + +void +gdm_chooser_widget_set_in_use_message (GdmChooserWidget *widget, + const char *message) +{ + g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); + + g_free (widget->priv->in_use_message); + widget->priv->in_use_message = g_strdup (message); +} + +void +gdm_chooser_widget_set_separator_position (GdmChooserWidget *widget, + GdmChooserWidgetPosition position) +{ + g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); + + if (widget->priv->separator_position != position) { + widget->priv->separator_position = position; + } + + gtk_tree_model_filter_refilter (widget->priv->model_filter); +} + +void +gdm_chooser_widget_set_hide_inactive_items (GdmChooserWidget *widget, + gboolean should_hide) +{ + widget->priv->should_hide_inactive_items = should_hide; + + if (should_hide && + (widget->priv->state != GDM_CHOOSER_WIDGET_STATE_SHRUNK + || widget->priv->state != GDM_CHOOSER_WIDGET_STATE_SHRINKING) && + widget->priv->active_row != NULL) { + gdm_chooser_widget_shrink (widget); + } else if (!should_hide && + (widget->priv->state != GDM_CHOOSER_WIDGET_STATE_GROWN + || widget->priv->state != GDM_CHOOSER_WIDGET_STATE_GROWING)) { + gdm_chooser_widget_grow (widget); + } +} + +int +gdm_chooser_widget_get_number_of_items (GdmChooserWidget *widget) +{ + return widget->priv->number_of_normal_rows + + widget->priv->number_of_separated_rows; +} + +void +gdm_chooser_widget_activate_if_one_item (GdmChooserWidget *widget) +{ + activate_if_one_item (widget); +} + +void +gdm_chooser_widget_propagate_pending_key_events (GdmChooserWidget *widget) +{ + if (!gdm_scrollable_widget_has_queued_key_events (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget))) { + return; + } + + gdm_scrollable_widget_replay_queued_key_events (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget)); +} + +void +gdm_chooser_widget_loaded (GdmChooserWidget *widget) +{ + g_signal_emit (widget, signals[LOADED], 0); + update_chooser_visibility (widget); + queue_move_cursor_to_top (widget); +} diff --git a/gui/simple-greeter/gdm-chooser-widget.h b/gui/simple-greeter/gdm-chooser-widget.h new file mode 100644 index 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 <rstrode@redhat.com> + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef __GDM_CHOOSER_WIDGET_H +#define __GDM_CHOOSER_WIDGET_H + +#include <glib-object.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_CHOOSER_WIDGET (gdm_chooser_widget_get_type ()) +#define GDM_CHOOSER_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_CHOOSER_WIDGET, GdmChooserWidget)) +#define GDM_CHOOSER_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_CHOOSER_WIDGET, GdmChooserWidgetClass)) +#define GDM_IS_CHOOSER_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_CHOOSER_WIDGET)) +#define GDM_IS_CHOOSER_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_CHOOSER_WIDGET)) +#define GDM_CHOOSER_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_CHOOSER_WIDGET, GdmChooserWidgetClass)) + +typedef struct GdmChooserWidgetPrivate GdmChooserWidgetPrivate; + +typedef struct +{ + GtkAlignment parent; + GdmChooserWidgetPrivate *priv; +} GdmChooserWidget; + +typedef struct +{ + GtkAlignmentClass parent_class; + + void (* loaded) (GdmChooserWidget *widget); + + void (* activated) (GdmChooserWidget *widget); + void (* deactivated) (GdmChooserWidget *widget); +} GdmChooserWidgetClass; + +typedef enum { + GDM_CHOOSER_WIDGET_POSITION_TOP = 0, + GDM_CHOOSER_WIDGET_POSITION_BOTTOM, +} GdmChooserWidgetPosition; + +typedef void (*GdmChooserWidgetItemLoadFunc) (GdmChooserWidget *widget, + const char *id, + gpointer data); + +typedef gboolean (*GdmChooserUpdateForeachFunc) (GdmChooserWidget *widget, + const char *id, + GdkPixbuf **image, + char **name, + char **comment, + gulong *priority, + gboolean *is_in_use, + gboolean *is_separate, + gpointer data); + +GType gdm_chooser_widget_get_type (void); +GtkWidget * gdm_chooser_widget_new (const char *unactive_label, + const char *active_label); + +void gdm_chooser_widget_add_item (GdmChooserWidget *widget, + const char *id, + GdkPixbuf *image, + const char *name, + const char *comment, + gulong priority, + gboolean is_in_use, + gboolean keep_separate, + GdmChooserWidgetItemLoadFunc load_func, + gpointer load_data); + +void gdm_chooser_widget_update_foreach_item (GdmChooserWidget *widget, + GdmChooserUpdateForeachFunc cb, + gpointer data); + +void gdm_chooser_widget_update_item (GdmChooserWidget *widget, + const char *id, + GdkPixbuf *new_image, + const char *new_name, + const char *new_comment, + gulong priority, + gboolean new_in_use, + gboolean new_is_separate); + +void gdm_chooser_widget_remove_item (GdmChooserWidget *widget, + const char *id); + +gboolean gdm_chooser_widget_lookup_item (GdmChooserWidget *widget, + const char *id, + GdkPixbuf **image, + char **name, + char **comment, + gulong *priority, + gboolean *is_in_use, + gboolean *is_separate); + +char * gdm_chooser_widget_get_selected_item (GdmChooserWidget *widget); +void gdm_chooser_widget_set_selected_item (GdmChooserWidget *widget, + const char *item); + +char * gdm_chooser_widget_get_active_item (GdmChooserWidget *widget); +void gdm_chooser_widget_set_active_item (GdmChooserWidget *widget, + const char *item); + +void gdm_chooser_widget_set_item_in_use (GdmChooserWidget *widget, + const char *id, + gboolean is_in_use); +void gdm_chooser_widget_set_item_priority (GdmChooserWidget *widget, + const char *id, + gulong priority); +void gdm_chooser_widget_set_item_timer (GdmChooserWidget *widget, + const char *id, + gulong timeout); +void gdm_chooser_widget_set_in_use_message (GdmChooserWidget *widget, + const char *message); + +void gdm_chooser_widget_set_separator_position (GdmChooserWidget *widget, + GdmChooserWidgetPosition position); +void gdm_chooser_widget_set_hide_inactive_items (GdmChooserWidget *widget, + gboolean should_hide); + +void gdm_chooser_widget_activate_selected_item (GdmChooserWidget *widget); + +int gdm_chooser_widget_get_number_of_items (GdmChooserWidget *widget); +void gdm_chooser_widget_activate_if_one_item (GdmChooserWidget *widget); +void gdm_chooser_widget_propagate_pending_key_events (GdmChooserWidget *widget); + +/* Protected + */ +void gdm_chooser_widget_loaded (GdmChooserWidget *widget); + +G_END_DECLS + +#endif /* __GDM_CHOOSER_WIDGET_H */ diff --git a/gui/simple-greeter/gdm-clock-widget.c b/gui/simple-greeter/gdm-clock-widget.c new file mode 100644 index 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 <mccann@jhu.edu> + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Written by: William Jon McCann <mccann@jhu.edu> + * Ray Strode <rstrode@redhat.com> + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <dirent.h> +#include <sys/stat.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gtk/gtk.h> + +#include "gdm-clock-widget.h" + +#define GDM_CLOCK_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_CLOCK_WIDGET, GdmClockWidgetPrivate)) + +struct GdmClockWidgetPrivate +{ + GtkWidget *label; + char *time_format; + char *tooltip_format; + guint update_clock_id; + guint should_show_seconds : 1; + guint should_show_date : 1; +}; + +static void gdm_clock_widget_class_init (GdmClockWidgetClass *klass); +static void gdm_clock_widget_init (GdmClockWidget *clock_widget); +static void gdm_clock_widget_finalize (GObject *object); +static gboolean update_timeout_cb (GdmClockWidget *clock); + +G_DEFINE_TYPE (GdmClockWidget, gdm_clock_widget, GTK_TYPE_ALIGNMENT) + +static void +update_time_format (GdmClockWidget *clock) +{ + char *clock_format; + char *tooltip_format; + + if (clock->priv->should_show_date && clock->priv->should_show_seconds) { + /* translators: This is the time format to use when both + * the date and time with seconds are being shown together. + */ + clock_format = _("%a %b %e, %l:%M:%S %p"); + tooltip_format = NULL; + } else if (clock->priv->should_show_date && !clock->priv->should_show_seconds) { + /* translators: This is the time format to use when both + * the date and time without seconds are being shown together. + */ + clock_format = _("%a %b %e, %l:%M %p"); + + tooltip_format = NULL; + } else if (!clock->priv->should_show_date && clock->priv->should_show_seconds) { + /* translators: This is the time format to use when there is + * no date, just weekday and time with seconds. + */ + clock_format = _("%a %l:%M:%S %p"); + + /* translators: This is the time format to use for the date + */ + tooltip_format = "%x"; + } else { + /* translators: This is the time format to use when there is + * no date, just weekday and time without seconds. + */ + clock_format = _("%a %l:%M %p"); + + tooltip_format = "%x"; + } + + g_free (clock->priv->time_format); + clock->priv->time_format = g_locale_from_utf8 (clock_format, -1, NULL, NULL, NULL); + + g_free (clock->priv->tooltip_format); + + if (tooltip_format != NULL) { + clock->priv->tooltip_format = g_locale_from_utf8 (tooltip_format, -1, NULL, NULL, NULL); + } else { + clock->priv->tooltip_format = NULL; + } +} + +static void +update_clock (GtkLabel *label, + const char *clock_format, + const char *tooltip_format) +{ + time_t t; + struct tm *tm; + char buf[256]; + char *utf8; + char *markup; + + time (&t); + tm = localtime (&t); + if (tm == NULL) { + g_warning ("Unable to get broken down local time"); + return; + } + if (strftime (buf, sizeof (buf), clock_format, tm) == 0) { + g_warning ("Couldn't format time: %s", clock_format); + strcpy (buf, "???"); + } + utf8 = g_locale_to_utf8 (buf, -1, NULL, NULL, NULL); + markup = g_strdup_printf ("<b><span foreground=\"white\">%s</span></b>", utf8); + gtk_label_set_markup (label, markup); + g_free (markup); + g_free (utf8); + + if (tooltip_format != NULL) { + if (strftime (buf, sizeof (buf), tooltip_format, tm) == 0) { + g_warning ("Couldn't format tooltip date: %s", tooltip_format); + strcpy (buf, "???"); + } + utf8 = g_locale_to_utf8 (buf, -1, NULL, NULL, NULL); + gtk_widget_set_tooltip_text (GTK_WIDGET (label), utf8); + g_free (utf8); + } else { + gtk_widget_set_has_tooltip (GTK_WIDGET (label), FALSE); + } +} + +static void +set_clock_timeout (GdmClockWidget *clock, + time_t now) +{ + GTimeVal tv; + int timeouttime; + + if (clock->priv->update_clock_id > 0) { + g_source_remove (clock->priv->update_clock_id); + clock->priv->update_clock_id = 0; + } + + g_get_current_time (&tv); + timeouttime = (G_USEC_PER_SEC - tv.tv_usec) / 1000 + 1; + + /* timeout of one minute if we don't care about the seconds */ + if (! clock->priv->should_show_seconds) { + timeouttime += 1000 * (59 - now % 60); + } + + clock->priv->update_clock_id = g_timeout_add (timeouttime, + (GSourceFunc)update_timeout_cb, + clock); +} + +static gboolean +update_timeout_cb (GdmClockWidget *clock) +{ + time_t new_time; + + time (&new_time); + + if (clock->priv->label != NULL) { + update_clock (GTK_LABEL (clock->priv->label), + clock->priv->time_format, + clock->priv->tooltip_format); + } + + set_clock_timeout (clock, new_time); + + return FALSE; +} + +static void +remove_timeout (GdmClockWidget *clock) +{ + if (clock->priv->update_clock_id > 0) { + g_source_remove (clock->priv->update_clock_id); + clock->priv->update_clock_id = 0; + } +} + +static void +gdm_clock_widget_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + if (GTK_WIDGET_CLASS (gdm_clock_widget_parent_class)->get_preferred_width) { + GTK_WIDGET_CLASS (gdm_clock_widget_parent_class)->get_preferred_width (widget, minimum_size, natural_size); + } + +} + +static void +gdm_clock_widget_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + PangoFontMetrics *metrics; + PangoContext *context; + int ascent; + int descent; + int padding; + int min_size; + int nat_size; + + min_size = 0; + nat_size = 0; + + if (GTK_WIDGET_CLASS (gdm_clock_widget_parent_class)->get_preferred_height) { + GTK_WIDGET_CLASS (gdm_clock_widget_parent_class)->get_preferred_height (widget, &min_size, &nat_size); + } + + gtk_widget_ensure_style (widget); + context = gtk_widget_get_pango_context (widget); + metrics = pango_context_get_metrics (context, + gtk_widget_get_style (widget)->font_desc, + pango_context_get_language (context)); + + ascent = pango_font_metrics_get_ascent (metrics); + descent = pango_font_metrics_get_descent (metrics); + padding = PANGO_PIXELS (ascent + descent) / 2.0; + min_size += padding; + nat_size += padding; + + if (minimum_size) + *minimum_size = min_size; + if (natural_size) + *natural_size = nat_size; + + pango_font_metrics_unref (metrics); +} + +static void +gdm_clock_widget_class_init (GdmClockWidgetClass *klass) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = G_OBJECT_CLASS (klass); + widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gdm_clock_widget_finalize; + widget_class->get_preferred_width = gdm_clock_widget_get_preferred_width; + widget_class->get_preferred_height = gdm_clock_widget_get_preferred_height; + + g_type_class_add_private (klass, sizeof (GdmClockWidgetPrivate)); +} + +static void +gdm_clock_widget_init (GdmClockWidget *widget) +{ + GtkWidget *box; + + widget->priv = GDM_CLOCK_WIDGET_GET_PRIVATE (widget); + + box = gtk_hbox_new (FALSE, 6); + gtk_widget_show (box); + gtk_container_add (GTK_CONTAINER (widget), box); + + widget->priv->label = gtk_label_new (""); + + gtk_widget_show (widget->priv->label); + gtk_box_pack_start (GTK_BOX (box), widget->priv->label, FALSE, FALSE, 0); + + update_time_format (widget); + update_timeout_cb (widget); +} + +static void +gdm_clock_widget_finalize (GObject *object) +{ + GdmClockWidget *clock_widget; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_CLOCK_WIDGET (object)); + + clock_widget = GDM_CLOCK_WIDGET (object); + + g_return_if_fail (clock_widget->priv != NULL); + + remove_timeout (clock_widget); + + G_OBJECT_CLASS (gdm_clock_widget_parent_class)->finalize (object); +} + +GtkWidget * +gdm_clock_widget_new (void) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_CLOCK_WIDGET, + NULL); + + return GTK_WIDGET (object); +} diff --git a/gui/simple-greeter/gdm-clock-widget.h b/gui/simple-greeter/gdm-clock-widget.h new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Written by: Ray Strode <rstrode@redhat.com> + * Jon McCann <mccann@jhu.edu> + */ + +#ifndef __GDM_CLOCK_WIDGET_H +#define __GDM_CLOCK_WIDGET_H + +#include <glib-object.h> + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_CLOCK_WIDGET (gdm_clock_widget_get_type ()) +#define GDM_CLOCK_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_CLOCK_WIDGET, GdmClockWidget)) +#define GDM_CLOCK_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_CLOCK_WIDGET, GdmClockWidgetClass)) +#define GDM_IS_CLOCK_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_CLOCK_WIDGET)) +#define GDM_IS_CLOCK_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_CLOCK_WIDGET)) +#define GDM_CLOCK_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_CLOCK_WIDGET, GdmClockWidgetClass)) + +typedef struct GdmClockWidgetPrivate GdmClockWidgetPrivate; + +typedef struct +{ + GtkAlignment parent; + GdmClockWidgetPrivate *priv; +} GdmClockWidget; + +typedef struct +{ + GtkAlignmentClass parent_class; +} GdmClockWidgetClass; + +GType gdm_clock_widget_get_type (void); +GtkWidget * gdm_clock_widget_new (void); + +#endif /* __GDM_CLOCK_WIDGET_H */ diff --git a/gui/simple-greeter/gdm-extension-list.c b/gui/simple-greeter/gdm-extension-list.c new file mode 100644 index 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 <rstrode@redhat.com> + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <dirent.h> +#include <sys/stat.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gtk/gtk.h> + +#include "gdm-extension-list.h" + +#define GDM_EXTENSION_LIST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_EXTENSION_LIST, GdmExtensionListPrivate)) +typedef gboolean (* GdmExtensionListForeachFunc) (GdmExtensionList *extension_list, + GdmLoginExtension *extension, + gpointer data); + + +struct GdmExtensionListPrivate +{ + GtkWidget *box; + GList *extensions; +}; + +enum { + ACTIVATED = 0, + DEACTIVATED, + NUMBER_OF_SIGNALS +}; + +static guint signals[NUMBER_OF_SIGNALS]; + +static void gdm_extension_list_class_init (GdmExtensionListClass *klass); +static void gdm_extension_list_init (GdmExtensionList *extension_list); +static void gdm_extension_list_finalize (GObject *object); + +G_DEFINE_TYPE (GdmExtensionList, gdm_extension_list, GTK_TYPE_ALIGNMENT); + +static void +on_extension_toggled (GdmExtensionList *widget, + GtkRadioButton *button) +{ + GdmLoginExtension *extension; + + extension = g_object_get_data (G_OBJECT (button), "gdm-extension"); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) { + + GList *extension_node; + /* Sort the list such that the extensions the user clicks last end + * up first. This doesn't change the order in which the extensions + * appear in the UI, but will affect which extensions we implicitly + * activate if the currently active extension gets disabled. + */ + extension_node = g_list_find (widget->priv->extensions, extension); + if (extension_node != NULL) { + widget->priv->extensions = g_list_delete_link (widget->priv->extensions, extension_node); + widget->priv->extensions = g_list_prepend (widget->priv->extensions, + extension); + } + + g_signal_emit (widget, signals[ACTIVATED], 0, extension); + } else { + g_signal_emit (widget, signals[DEACTIVATED], 0, extension); + } +} + +static GdmLoginExtension * +gdm_extension_list_foreach_extension (GdmExtensionList *extension_list, + GdmExtensionListForeachFunc search_func, + gpointer data) +{ + GList *node; + + for (node = extension_list->priv->extensions; node != NULL; node = node->next) { + GdmLoginExtension *extension; + + extension = node->data; + + if (search_func (extension_list, extension, data)) { + return g_object_ref (extension); + } + } + + return NULL; +} + +static void +on_extension_enabled (GdmExtensionList *extension_list, + GdmLoginExtension *extension) +{ + GtkWidget *button; + + button = g_object_get_data (G_OBJECT (extension), "gdm-extension-list-button"); + + gtk_widget_set_sensitive (button, TRUE); +} + +static gboolean +gdm_extension_list_set_active_extension (GdmExtensionList *widget, + GdmLoginExtension *extension) +{ + GtkWidget *button; + gboolean was_sensitive; + gboolean was_activated; + + if (!gdm_login_extension_is_visible (extension)) { + return FALSE; + } + + was_sensitive = gtk_widget_get_sensitive (GTK_WIDGET (widget)); + gtk_widget_set_sensitive (GTK_WIDGET (widget), TRUE); + + button = GTK_WIDGET (g_object_get_data (G_OBJECT (extension), + "gdm-extension-list-button")); + + was_activated = FALSE; + if (gtk_widget_is_sensitive (button)) { + if (gtk_widget_activate (button)) { + was_activated = TRUE; + } + } + + gtk_widget_set_sensitive (GTK_WIDGET (widget), was_sensitive); + return was_activated; +} + +static void +activate_first_available_extension (GdmExtensionList *extension_list) +{ + GList *node; + + node = extension_list->priv->extensions; + while (node != NULL) { + GdmLoginExtension *extension; + + extension = GDM_LOGIN_EXTENSION (node->data); + + if (gdm_extension_list_set_active_extension (extension_list, extension)) { + break; + } + + node = node->next; + } +} + +static void +on_extension_disabled (GdmExtensionList *extension_list, + GdmLoginExtension *extension) +{ + GtkWidget *button; + gboolean was_active; + + button = g_object_get_data (G_OBJECT (extension), "gdm-extension-list-button"); + was_active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); + + gtk_widget_set_sensitive (button, FALSE); + + if (was_active) { + activate_first_available_extension (extension_list); + } +} + +void +gdm_extension_list_add_extension (GdmExtensionList *extension_list, + GdmLoginExtension *extension) +{ + GtkWidget *image; + GtkWidget *button; + GIcon *icon; + char *description; + + if (extension_list->priv->extensions == NULL) { + button = gtk_radio_button_new (NULL); + } else { + GdmLoginExtension *previous_extension; + GtkRadioButton *previous_button; + + previous_extension = GDM_LOGIN_EXTENSION (extension_list->priv->extensions->data); + previous_button = GTK_RADIO_BUTTON (g_object_get_data (G_OBJECT (previous_extension), "gdm-extension-list-button")); + button = gtk_radio_button_new_from_widget (previous_button); + } + g_object_set_data (G_OBJECT (extension), "gdm-extension-list-button", button); + + g_object_set (G_OBJECT (button), "draw-indicator", FALSE, NULL); + g_object_set_data (G_OBJECT (button), "gdm-extension", extension); + g_signal_connect_swapped (button, "toggled", + G_CALLBACK (on_extension_toggled), + extension_list); + + gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE); + gtk_widget_set_sensitive (button, gdm_login_extension_is_enabled (extension)); + + g_signal_connect_swapped (G_OBJECT (extension), "enabled", + G_CALLBACK (on_extension_enabled), + extension_list); + + g_signal_connect_swapped (G_OBJECT (extension), "disabled", + G_CALLBACK (on_extension_disabled), + extension_list); + + icon = gdm_login_extension_get_icon (extension); + image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_SMALL_TOOLBAR); + g_object_unref (icon); + + gtk_widget_show (image); + gtk_container_add (GTK_CONTAINER (button), image); + description = gdm_login_extension_get_description (extension); + gtk_widget_set_tooltip_text (button, description); + g_free (description); + gtk_widget_show (button); + + gtk_container_add (GTK_CONTAINER (extension_list->priv->box), button); + extension_list->priv->extensions = g_list_append (extension_list->priv->extensions, + g_object_ref (extension)); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) { + g_signal_emit (extension_list, signals[ACTIVATED], 0, extension); + } +} + +void +gdm_extension_list_remove_extension (GdmExtensionList *extension_list, + GdmLoginExtension *extension) +{ + GtkWidget *button; + GList *node; + + node = g_list_find (extension_list->priv->extensions, extension); + + if (node == NULL) { + return; + } + + extension_list->priv->extensions = g_list_delete_link (extension_list->priv->extensions, node); + + button = g_object_get_data (G_OBJECT (extension), "gdm-extension-list-button"); + + if (button != NULL) { + g_signal_handlers_disconnect_by_func (G_OBJECT (extension), + G_CALLBACK (on_extension_enabled), + extension_list); + g_signal_handlers_disconnect_by_func (G_OBJECT (extension), + G_CALLBACK (on_extension_disabled), + extension_list); + gtk_widget_destroy (button); + g_object_set_data (G_OBJECT (extension), "gdm-extension-list-button", NULL); + } + + g_object_unref (extension); + + activate_first_available_extension (extension_list); +} + +static void +gdm_extension_list_class_init (GdmExtensionListClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gdm_extension_list_finalize; + + signals [ACTIVATED] = g_signal_new ("activated", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmExtensionListClass, activated), + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, G_TYPE_OBJECT); + + signals [DEACTIVATED] = g_signal_new ("deactivated", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmExtensionListClass, deactivated), + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, G_TYPE_OBJECT); + + g_type_class_add_private (klass, sizeof (GdmExtensionListPrivate)); +} + +static void +gdm_extension_list_init (GdmExtensionList *widget) +{ + widget->priv = GDM_EXTENSION_LIST_GET_PRIVATE (widget); + + gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 0, 0, 0); + gtk_alignment_set (GTK_ALIGNMENT (widget), 0.0, 0.0, 0, 0); + + widget->priv->box = gtk_hbox_new (TRUE, 2); + gtk_widget_show (widget->priv->box); + gtk_container_add (GTK_CONTAINER (widget), + widget->priv->box); +} + +static void +gdm_extension_list_finalize (GObject *object) +{ + GdmExtensionList *widget; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_EXTENSION_LIST (object)); + + widget = GDM_EXTENSION_LIST (object); + + g_list_foreach (widget->priv->extensions, (GFunc) g_object_unref, NULL); + g_list_free (widget->priv->extensions); + + G_OBJECT_CLASS (gdm_extension_list_parent_class)->finalize (object); +} + +GtkWidget * +gdm_extension_list_new (void) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_EXTENSION_LIST, NULL); + + return GTK_WIDGET (object); +} + +static gboolean +gdm_extension_list_extension_is_active (GdmExtensionList *extension_list, + GdmLoginExtension *extension) +{ + GtkWidget *button; + + button = g_object_get_data (G_OBJECT (extension), "gdm-extension-list-button"); + + return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); +} + +GdmLoginExtension * +gdm_extension_list_get_active_extension (GdmExtensionList *widget) +{ + return gdm_extension_list_foreach_extension (widget, + (GdmExtensionListForeachFunc) + gdm_extension_list_extension_is_active, + NULL); +} + +int +gdm_extension_list_get_number_of_visible_extensions (GdmExtensionList *widget) +{ + GList *node; + int number_of_visible_extensions; + + number_of_visible_extensions = 0; + for (node = widget->priv->extensions; node != NULL; node = node->next) { + GdmLoginExtension *extension; + + extension = node->data; + + if (gdm_login_extension_is_enabled (extension) && gdm_login_extension_is_visible (extension)) { + number_of_visible_extensions++; + } + } + + return number_of_visible_extensions; +} diff --git a/gui/simple-greeter/gdm-extension-list.h b/gui/simple-greeter/gdm-extension-list.h new file mode 100644 index 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 <rstrode@redhat.com> + */ + +#ifndef __GDM_EXTENSION_LIST_H +#define __GDM_EXTENSION_LIST_H + +#include <glib-object.h> +#include <gio/gio.h> +#include <gtk/gtk.h> + +#include "gdm-login-extension.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_EXTENSION_LIST (gdm_extension_list_get_type ()) +#define GDM_EXTENSION_LIST(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_EXTENSION_LIST, GdmExtensionList)) +#define GDM_EXTENSION_LIST_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_EXTENSION_LIST, GdmExtensionListClass)) +#define GDM_IS_EXTENSION_LIST(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_EXTENSION_LIST)) +#define GDM_IS_EXTENSION_LIST_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_EXTENSION_LIST)) +#define GDM_EXTENSION_LIST_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_EXTENSION_LIST, GdmExtensionListClass)) + +typedef struct GdmExtensionListPrivate GdmExtensionListPrivate; +typedef struct _GdmExtensionList GdmExtensionList; + +struct _GdmExtensionList +{ + GtkAlignment parent; + GdmExtensionListPrivate *priv; +}; + +typedef struct +{ + GtkAlignmentClass parent_class; + + void (* deactivated) (GdmExtensionList *widget, + GdmLoginExtension *extension); + void (* activated) (GdmExtensionList *widget, + GdmLoginExtension *extension); +} GdmExtensionListClass; + +GType gdm_extension_list_get_type (void); +GtkWidget * gdm_extension_list_new (void); + +GdmLoginExtension *gdm_extension_list_get_active_extension (GdmExtensionList *widget); +void gdm_extension_list_add_extension (GdmExtensionList *widget, + GdmLoginExtension *extension); +void gdm_extension_list_remove_extension (GdmExtensionList *widget, + GdmLoginExtension *extension); + +int gdm_extension_list_get_number_of_visible_extensions (GdmExtensionList *widget); +G_END_DECLS + +#endif /* __GDM_EXTENSION_LIST_H */ diff --git a/gui/simple-greeter/gdm-greeter-login-window.c b/gui/simple-greeter/gdm-greeter-login-window.c new file mode 100644 index 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 <mccann@jhu.edu> + * Copyright (C) 2008, 2009 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Written by: William Jon McCann <mccann@jhu.edu> + * Ray Strode <rstrode@redhat.com> + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <errno.h> +#include <pwd.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-object.h> +#include <gio/gio.h> + +#include <gdk/gdkkeysyms.h> +#include <gdk/gdkx.h> +#include <X11/XKBlib.h> + +#include <gtk/gtk.h> + +#include "gdm-settings-client.h" +#include "gdm-settings-keys.h" +#include "gdm-profile.h" + +#include "gdm-client.h" +#include "gdm-greeter-login-window.h" +#include "gdm-user-chooser-widget.h" +#include "gdm-session-option-widget.h" +#include "gdm-extension-list.h" + +#include "extensions/unified/gdm-unified-extension.h" + +#ifdef HAVE_PAM +#include <security/pam_appl.h> +#define PW_ENTRY_SIZE PAM_MAX_RESP_SIZE +#else +#define PW_ENTRY_SIZE GDM_MAX_PASS +#endif + +#define CK_NAME "org.freedesktop.ConsoleKit" +#define CK_PATH "/org/freedesktop/ConsoleKit" +#define CK_INTERFACE "org.freedesktop.ConsoleKit" + +#define CK_MANAGER_PATH "/org/freedesktop/ConsoleKit/Manager" +#define CK_MANAGER_INTERFACE "org.freedesktop.ConsoleKit.Manager" +#define CK_SEAT_INTERFACE "org.freedesktop.ConsoleKit.Seat" +#define CK_SESSION_INTERFACE "org.freedesktop.ConsoleKit.Session" + +#define UI_XML_FILE "gdm-greeter-login-window.ui" + +#define LOGIN_SCREEN_SCHEMA "org.gnome.login-screen" +#define KEY_BANNER_MESSAGE_ENABLED "banner-message-enable" +#define KEY_BANNER_MESSAGE_TEXT "banner-message-text" +#define KEY_LOGO "fallback-logo" +#define KEY_DISABLE_USER_LIST "disable-user-list" + +#define LSB_RELEASE_COMMAND "lsb_release -d" + +#define GDM_GREETER_LOGIN_WINDOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_GREETER_LOGIN_WINDOW, GdmGreeterLoginWindowPrivate)) +#define GDM_CUSTOM_SESSION "custom" + +#define INFO_MESSAGE_DURATION 2 +#define PROBLEM_MESSAGE_DURATION 3 + +enum { + MODE_UNDEFINED = 0, + MODE_TIMED_LOGIN, + MODE_SELECTION, + MODE_AUTHENTICATION, + MODE_MULTIPLE_AUTHENTICATION, +}; + +enum { + LOGIN_BUTTON_HIDDEN = 0, + LOGIN_BUTTON_ANSWER_QUERY, + LOGIN_BUTTON_TIMED_LOGIN +}; + +struct GdmGreeterLoginWindowPrivate +{ + GtkBuilder *builder; + GtkWidget *session_option_widget; + GtkWidget *user_chooser; + GtkWidget *extension_list; + GtkWidget *auth_banner_label; + GtkWidget *current_button; + GtkWidget *auth_page_box; + guint display_is_local : 1; + guint user_chooser_loaded : 1; + GSettings *settings; + GList *extensions; + GdmLoginExtension *active_extension; + GList *extensions_to_enable; + GList *extensions_to_stop; + + gboolean banner_message_enabled; + gulong gsettings_cnxn; + + guint last_mode; + guint dialog_mode; + guint next_mode; + + gboolean user_list_disabled; + guint num_queries; + + gboolean timed_login_already_enabled; + gboolean timed_login_enabled; + guint timed_login_delay; + char *timed_login_username; + guint timed_login_timeout_id; + + guint login_button_handler_id; + guint start_session_handler_id; + + char *service_name_of_session_ready_to_start; +}; + +enum { + PROP_0, + PROP_DISPLAY_IS_LOCAL, +}; + +enum { + START_CONVERSATION, + BEGIN_AUTO_LOGIN, + BEGIN_VERIFICATION, + BEGIN_VERIFICATION_FOR_USER, + QUERY_ANSWER, + START_SESSION, + USER_SELECTED, + SESSION_SELECTED, + CANCELLED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gdm_greeter_login_window_class_init (GdmGreeterLoginWindowClass *klass); +static void gdm_greeter_login_window_init (GdmGreeterLoginWindow *greeter_login_window); +static void gdm_greeter_login_window_finalize (GObject *object); + +static void restart_timed_login_timeout (GdmGreeterLoginWindow *login_window); +static void on_user_unchosen (GdmUserChooserWidget *user_chooser, + GdmGreeterLoginWindow *login_window); + +static void switch_mode (GdmGreeterLoginWindow *login_window, + int number); +static void update_banner_message (GdmGreeterLoginWindow *login_window); +static void reset_dialog (GdmGreeterLoginWindow *login_window, + guint dialog_mode); +static void gdm_greeter_login_window_start_session_when_ready (GdmGreeterLoginWindow *login_window, + const char *service_name); +static void handle_stopped_conversation (GdmGreeterLoginWindow *login_window, + const char *service_name); + +static void begin_single_service_verification (GdmGreeterLoginWindow *login_window, + const char *service_name); + +G_DEFINE_TYPE (GdmGreeterLoginWindow, gdm_greeter_login_window, GTK_TYPE_WINDOW) + +static void +set_busy (GdmGreeterLoginWindow *login_window) +{ + GdkCursor *cursor; + + cursor = gdk_cursor_new (GDK_WATCH); + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (login_window)), cursor); + g_object_unref (cursor); +} + +static void +set_ready (GdmGreeterLoginWindow *login_window) +{ + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (login_window)), NULL); +} + +static void +set_sensitive (GdmGreeterLoginWindow *login_window, + gboolean sensitive) +{ + GtkWidget *box; + + box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "buttonbox")); + gtk_widget_set_sensitive (box, sensitive); + + gtk_widget_set_sensitive (login_window->priv->user_chooser, sensitive); +} + +static void +set_focus (GdmGreeterLoginWindow *login_window) +{ + gdk_window_focus (gtk_widget_get_window (GTK_WIDGET (login_window)), GDK_CURRENT_TIME); + + if (login_window->priv->active_extension != NULL && + gdm_login_extension_focus (login_window->priv->active_extension)) { + char *name; + name = gdm_login_extension_get_name (login_window->priv->active_extension); + g_debug ("GdmGreeterLoginWindow: focusing extension %s", name); + g_free (name); + } else if (gtk_widget_get_realized (login_window->priv->user_chooser) && ! gtk_widget_has_focus (login_window->priv->user_chooser)) { + gtk_widget_grab_focus (login_window->priv->user_chooser); + } + +} + +static gboolean +queue_message_for_extension (GdmLoginExtension *extension, + const char *message) +{ + gdm_login_extension_queue_message (extension, + GDM_SERVICE_MESSAGE_TYPE_INFO, + message); + return FALSE; +} + +static void +set_message (GdmGreeterLoginWindow *login_window, + const char *text) +{ + g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window)); + + g_list_foreach (login_window->priv->extensions, + (GFunc) queue_message_for_extension, + (gpointer) text); +} + +static void +on_user_interaction (GdmGreeterLoginWindow *login_window) +{ + g_debug ("GdmGreeterLoginWindow: user is interacting with session!\n"); + restart_timed_login_timeout (login_window); +} + +static GdkFilterReturn +on_xevent (XEvent *xevent, + GdkEvent *event, + GdmGreeterLoginWindow *login_window) +{ + switch (xevent->xany.type) { + case KeyPress: + case KeyRelease: + case ButtonPress: + case ButtonRelease: + on_user_interaction (login_window); + break; + case PropertyNotify: + if (xevent->xproperty.atom == gdk_x11_get_xatom_by_name ("_NET_WM_USER_TIME")) { + on_user_interaction (login_window); + } + break; + + default: + break; + } + + return GDK_FILTER_CONTINUE; +} + +static void +stop_watching_for_user_interaction (GdmGreeterLoginWindow *login_window) +{ + gdk_window_remove_filter (NULL, + (GdkFilterFunc) on_xevent, + login_window); +} + +static void +remove_timed_login_timeout (GdmGreeterLoginWindow *login_window) +{ + if (login_window->priv->timed_login_timeout_id > 0) { + g_debug ("GdmGreeterLoginWindow: removing timed login timer"); + g_source_remove (login_window->priv->timed_login_timeout_id); + login_window->priv->timed_login_timeout_id = 0; + } + + stop_watching_for_user_interaction (login_window); +} + +static gboolean +timed_login_timer (GdmGreeterLoginWindow *login_window) +{ + set_sensitive (login_window, FALSE); + set_message (login_window, _("Automatically logging in…")); + + g_debug ("GdmGreeterLoginWindow: timer expired"); + login_window->priv->timed_login_timeout_id = 0; + + return FALSE; +} + +static void +watch_for_user_interaction (GdmGreeterLoginWindow *login_window) +{ + gdk_window_add_filter (NULL, + (GdkFilterFunc) on_xevent, + login_window); +} + +static void +restart_timed_login_timeout (GdmGreeterLoginWindow *login_window) +{ + remove_timed_login_timeout (login_window); + + if (login_window->priv->timed_login_enabled) { + g_debug ("GdmGreeterLoginWindow: adding timed login timer"); + watch_for_user_interaction (login_window); + login_window->priv->timed_login_timeout_id = g_timeout_add_seconds (login_window->priv->timed_login_delay, + (GSourceFunc)timed_login_timer, + login_window); + + gdm_chooser_widget_set_item_timer (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser), + GDM_USER_CHOOSER_USER_AUTO, + login_window->priv->timed_login_delay * 1000); + } +} + +static void +show_widget (GdmGreeterLoginWindow *login_window, + const char *name, + gboolean visible) +{ + GtkWidget *widget; + + widget = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, name)); + if (widget != NULL) { + if (visible) { + gtk_widget_show (widget); + } else { + gtk_widget_hide (widget); + } + } +} + +static void +hide_extension_actions (GdmLoginExtension *extension) +{ + GtkActionGroup *actions; + + actions = gdm_login_extension_get_actions (extension); + + if (actions != NULL) { + gtk_action_group_set_visible (actions, FALSE); + gtk_action_group_set_sensitive (actions, FALSE); + g_object_unref (actions); + } +} + +static void +grab_default_button_for_extension (GdmLoginExtension *extension) +{ + GtkActionGroup *actions; + GtkAction *action; + GSList *proxies, *node; + + actions = gdm_login_extension_get_actions (extension); + + if (actions == NULL) { + return; + } + + action = gtk_action_group_get_action (actions, GDM_LOGIN_EXTENSION_DEFAULT_ACTION); + g_object_unref (actions); + + if (action == NULL) { + return; + } + + proxies = gtk_action_get_proxies (action); + for (node = proxies; node != NULL; node = node->next) { + GtkWidget *widget; + + widget = GTK_WIDGET (node->data); + + if (gtk_widget_get_can_default (widget) && + gtk_widget_get_visible (widget)) { + gtk_widget_grab_default (widget); + break; + } + } +} + +static void +show_extension_actions (GdmLoginExtension *extension) +{ + GtkActionGroup *actions; + + actions = gdm_login_extension_get_actions (extension); + if (actions != NULL) { + gtk_action_group_set_sensitive (actions, TRUE); + gtk_action_group_set_visible (actions, TRUE); + g_object_unref (actions); + } +} + +static void +on_login_button_clicked_timed_login (GtkButton *button, + GdmGreeterLoginWindow *login_window) +{ + set_busy (login_window); + set_sensitive (login_window, FALSE); +} + +static void +set_log_in_button_mode (GdmGreeterLoginWindow *login_window, + int mode) +{ + GtkWidget *button; + GtkWidget *login_button; + GtkWidget *unlock_button; + char *item; + gboolean in_use; + + in_use = FALSE; + item = gdm_chooser_widget_get_active_item (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser)); + if (item != NULL) { + gboolean res; + + res = gdm_chooser_widget_lookup_item (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser), + item, + NULL, /* image */ + NULL, /* name */ + NULL, /* comment */ + NULL, /* priority */ + &in_use, + NULL); /* is separate */ + + if (!res) { + in_use = FALSE; + } + } + + if (login_window->priv->current_button != NULL) { + /* disconnect any signals */ + if (login_window->priv->login_button_handler_id > 0) { + g_signal_handler_disconnect (login_window->priv->current_button, + login_window->priv->login_button_handler_id); + login_window->priv->login_button_handler_id = 0; + } + } + + unlock_button = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "unlock-button")); + login_button = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "log-in-button")); + + if (in_use) { + gtk_widget_hide (login_button); + button = unlock_button; + } else { + gtk_widget_hide (unlock_button); + button = login_button; + } + gtk_widget_grab_default (button); + + login_window->priv->current_button = button; + + g_list_foreach (login_window->priv->extensions, (GFunc) hide_extension_actions, NULL); + + switch (mode) { + case LOGIN_BUTTON_HIDDEN: + if (login_window->priv->active_extension != NULL) { + hide_extension_actions (login_window->priv->active_extension); + } + + gtk_widget_hide (button); + break; + case LOGIN_BUTTON_ANSWER_QUERY: + if (login_window->priv->active_extension != NULL) { + show_extension_actions (login_window->priv->active_extension); + grab_default_button_for_extension (login_window->priv->active_extension); + } + + gtk_widget_hide (button); + break; + case LOGIN_BUTTON_TIMED_LOGIN: + login_window->priv->login_button_handler_id = g_signal_connect (button, "clicked", G_CALLBACK (on_login_button_clicked_timed_login), login_window); + gtk_widget_show (button); + break; + default: + g_assert_not_reached (); + break; + } +} + +static gboolean +user_chooser_has_no_user (GdmGreeterLoginWindow *login_window) +{ + guint num_items; + + num_items = gdm_chooser_widget_get_number_of_items (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser)); + g_debug ("GdmGreeterLoginWindow: loaded=%d num_items=%d", + login_window->priv->user_chooser_loaded, + num_items); + return (login_window->priv->user_chooser_loaded && num_items == 0); +} + +static void +maybe_show_cancel_button (GdmGreeterLoginWindow *login_window) +{ + gboolean show; + + show = FALSE; + + /* only show the cancel button if there is something to go + back to */ + + switch (login_window->priv->dialog_mode) { + case MODE_SELECTION: + /* should never have anything to return to from here */ + show = FALSE; + break; + case MODE_TIMED_LOGIN: + /* should always have something to return to from here */ + show = TRUE; + break; + case MODE_AUTHENTICATION: + case MODE_MULTIPLE_AUTHENTICATION: + if (login_window->priv->num_queries > 1) { + /* if we are inside a pam conversation past + the first step */ + show = TRUE; + } else { + if (login_window->priv->user_list_disabled || user_chooser_has_no_user (login_window)) { + show = FALSE; + } else { + show = TRUE; + } + } + break; + default: + g_assert_not_reached (); + } + + show_widget (login_window, "cancel-button", show); +} + +static void +update_extension_list_visibility (GdmGreeterLoginWindow *login_window) +{ + int number_of_extensions; + + if (login_window->priv->dialog_mode != MODE_MULTIPLE_AUTHENTICATION) { + gtk_widget_hide (login_window->priv->extension_list); + return; + } + + number_of_extensions = gdm_extension_list_get_number_of_visible_extensions (GDM_EXTENSION_LIST (login_window->priv->extension_list)); + if (number_of_extensions > 1) { + gtk_widget_show (login_window->priv->extension_list); + } else { + gtk_widget_hide (login_window->priv->extension_list); + } +} + +static void +switch_mode (GdmGreeterLoginWindow *login_window, + int number) +{ + GtkWidget *box; + + /* Should never switch to MODE_UNDEFINED */ + g_assert (number != MODE_UNDEFINED); + + /* we want to run this even if we're supposed to + be in the mode already so that we reset everything + to a known state */ + if (login_window->priv->dialog_mode != number) { + login_window->priv->last_mode = login_window->priv->dialog_mode; + login_window->priv->dialog_mode = number; + } + + login_window->priv->next_mode = MODE_UNDEFINED; + + switch (number) { + case MODE_SELECTION: + set_log_in_button_mode (login_window, LOGIN_BUTTON_HIDDEN); + set_sensitive (login_window, TRUE); + gtk_widget_hide (login_window->priv->session_option_widget); + break; + case MODE_TIMED_LOGIN: + set_log_in_button_mode (login_window, LOGIN_BUTTON_TIMED_LOGIN); + set_sensitive (login_window, TRUE); + gtk_widget_show (login_window->priv->session_option_widget); + break; + case MODE_AUTHENTICATION: + case MODE_MULTIPLE_AUTHENTICATION: + set_log_in_button_mode (login_window, LOGIN_BUTTON_ANSWER_QUERY); + set_sensitive (login_window, FALSE); + gtk_widget_show (login_window->priv->session_option_widget); + break; + default: + g_assert_not_reached (); + } + + show_widget (login_window, "auth-input-box", FALSE); + update_extension_list_visibility (login_window); + maybe_show_cancel_button (login_window); + + /* + * The rest of this function sets up the user list, so just return if + * the user list is disabled. + */ + if (login_window->priv->user_list_disabled && number != MODE_TIMED_LOGIN) { + return; + } + + box = gtk_widget_get_parent (login_window->priv->user_chooser); + if (GTK_IS_BOX (box)) { + guint padding; + GtkPackType pack_type; + + gtk_box_query_child_packing (GTK_BOX (box), + login_window->priv->user_chooser, + NULL, + NULL, + &padding, + &pack_type); + gtk_box_set_child_packing (GTK_BOX (box), + login_window->priv->user_chooser, + number == MODE_SELECTION, + number == MODE_SELECTION, + padding, + pack_type); + } +} + +static GdmLoginExtension * +find_extension_with_service_name (GdmGreeterLoginWindow *login_window, + const char *service_name) +{ + GList *node; + + node = login_window->priv->extensions; + while (node != NULL) { + GdmLoginExtension *extension; + char *extension_service_name; + gboolean has_service_name; + + extension = GDM_LOGIN_EXTENSION (node->data); + + extension_service_name = gdm_login_extension_get_service_name (extension); + has_service_name = strcmp (service_name, extension_service_name) == 0; + g_free (extension_service_name); + + if (has_service_name) { + return extension; + } + + node = node->next; + } + + return NULL; +} + +static gboolean +reset_extension (GdmLoginExtension *extension, + GdmGreeterLoginWindow *login_window) +{ + char *name; + + name = gdm_login_extension_get_name (extension); + g_debug ("Resetting extension '%s'", name); + g_free (name); + + login_window->priv->extensions_to_enable = g_list_remove (login_window->priv->extensions_to_enable, extension); + + hide_extension_actions (extension); + gdm_extension_list_remove_extension (GDM_EXTENSION_LIST (login_window->priv->extension_list), extension); + gdm_login_extension_reset (extension); + return FALSE; +} + +static gboolean +extensions_are_enabled (GdmGreeterLoginWindow *login_window) +{ + + GList *node; + + node = login_window->priv->extensions; + while (node != NULL) { + GdmLoginExtension *extension; + + extension = GDM_LOGIN_EXTENSION (node->data); + + if (!gdm_login_extension_is_enabled (extension)) { + return FALSE; + } + + node = node->next; + } + + return TRUE; +} + +static gboolean +can_jump_to_authenticate (GdmGreeterLoginWindow *login_window) +{ + gboolean res; + + if (!login_window->priv->user_chooser_loaded) { + res = FALSE; + } else if (!extensions_are_enabled (login_window)) { + res = FALSE; + } else if (login_window->priv->dialog_mode == MODE_AUTHENTICATION) { + res = FALSE; + } else if (login_window->priv->dialog_mode == MODE_MULTIPLE_AUTHENTICATION) { + res = FALSE; + } else if (login_window->priv->user_list_disabled) { + res = (login_window->priv->timed_login_username == NULL); + } else { + res = user_chooser_has_no_user (login_window); + } + + return res; +} + +static void +begin_other_verification (GdmGreeterLoginWindow *login_window) +{ + /* FIXME: we should drop this code and do all OTHER handling + * entirely from within the extension + * (ala how smart card manages its "Smartcard Authentication" item) + */ + if (find_extension_with_service_name (login_window, "gdm-password") != NULL) { + begin_single_service_verification (login_window, "gdm-password"); + } else { + begin_single_service_verification (login_window, "gdm"); + } +} + +static void +set_extension_active (GdmGreeterLoginWindow *login_window, + GdmLoginExtension *extension) +{ + GtkWidget *container; + char *name; + + name = gdm_login_extension_get_name (extension); + g_debug ("GdmGreeterLoginWindow: extension '%s' activated", name); + g_free (name); + + container = g_object_get_data (G_OBJECT (extension), + "gdm-greeter-login-window-page-container"); + + if (container == NULL) { + GtkWidget *page; + + container = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); + gtk_container_add (GTK_CONTAINER (login_window->priv->auth_page_box), + container); + + page = gdm_login_extension_get_page (extension); + if (page != NULL) { + gtk_container_add (GTK_CONTAINER (container), page); + gtk_widget_show (page); + } + g_object_set_data (G_OBJECT (extension), + "gdm-greeter-login-window-page-container", + container); + } + + gtk_widget_show (container); + + login_window->priv->active_extension = extension; + switch_mode (login_window, login_window->priv->dialog_mode); +} + +static void +clear_active_extension (GdmGreeterLoginWindow *login_window) +{ + + GtkWidget *container; + GtkActionGroup *actions; + + if (login_window->priv->active_extension == NULL) { + return; + } + + container = g_object_get_data (G_OBJECT (login_window->priv->active_extension), + "gdm-greeter-login-window-page-container"); + + if (container != NULL) { + gtk_widget_hide (container); + } + + actions = gdm_login_extension_get_actions (login_window->priv->active_extension); + + if (actions != NULL) { + gtk_action_group_set_sensitive (actions, FALSE); + gtk_action_group_set_visible (actions, FALSE); + g_object_unref (actions); + } + + login_window->priv->active_extension = NULL; +} + +static void +reset_dialog (GdmGreeterLoginWindow *login_window, + guint dialog_mode) +{ + g_debug ("GdmGreeterLoginWindow: Resetting dialog to mode %u", dialog_mode); + set_busy (login_window); + set_sensitive (login_window, FALSE); + + login_window->priv->num_queries = 0; + + g_free (login_window->priv->service_name_of_session_ready_to_start); + login_window->priv->service_name_of_session_ready_to_start = NULL; + + if (dialog_mode == MODE_SELECTION) { + if (login_window->priv->timed_login_enabled) { + gdm_chooser_widget_set_item_timer (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser), + GDM_USER_CHOOSER_USER_AUTO, 0); + remove_timed_login_timeout (login_window); + login_window->priv->timed_login_enabled = FALSE; + } + + g_signal_handlers_block_by_func (G_OBJECT (login_window->priv->user_chooser), + G_CALLBACK (on_user_unchosen), login_window); + gdm_user_chooser_widget_set_chosen_user_name (GDM_USER_CHOOSER_WIDGET (login_window->priv->user_chooser), NULL); + g_signal_handlers_unblock_by_func (G_OBJECT (login_window->priv->user_chooser), + G_CALLBACK (on_user_unchosen), login_window); + + if (login_window->priv->start_session_handler_id > 0) { + g_signal_handler_disconnect (login_window, login_window->priv->start_session_handler_id); + login_window->priv->start_session_handler_id = 0; + } + + set_message (login_window, ""); + } + + g_list_foreach (login_window->priv->extensions, (GFunc) reset_extension, login_window); + + if (can_jump_to_authenticate (login_window)) { + /* If we don't have a user list jump straight to authenticate */ + g_debug ("GdmGreeterLoginWindow: jumping straight to authenticate"); + g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED], + 0, GDM_USER_CHOOSER_USER_OTHER); + begin_other_verification (login_window); + } else { + clear_active_extension (login_window); + switch_mode (login_window, dialog_mode); + } + + gtk_widget_set_sensitive (login_window->priv->extension_list, TRUE); + set_ready (login_window); + set_focus (GDM_GREETER_LOGIN_WINDOW (login_window)); + update_banner_message (login_window); + + if (gdm_chooser_widget_get_number_of_items (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser)) >= 1) { + gdm_chooser_widget_propagate_pending_key_events (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser)); + } +} + +static void +restart_conversations (GdmGreeterLoginWindow *login_window) +{ + set_busy (login_window); + set_sensitive (login_window, FALSE); + g_signal_emit (login_window, signals[CANCELLED], 0); +} + +static gboolean +has_queued_messages (GdmGreeterLoginWindow *login_window) +{ + GList *node; + + node = login_window->priv->extensions; + while (node != NULL) { + GdmLoginExtension *extension; + + extension = (GdmLoginExtension *) node->data; + + if (gdm_login_extension_has_queued_messages (extension)) { + return TRUE; + } + node = node->next; + } + + return FALSE; +} + +static void +reset_dialog_after_messages (GdmGreeterLoginWindow *login_window, + guint dialog_mode) +{ + if (has_queued_messages (login_window)) { + g_debug ("GdmGreeterLoginWindow: will reset dialog after pending messages"); + login_window->priv->next_mode = dialog_mode; + } else { + g_debug ("GdmGreeterLoginWindow: resetting dialog"); + reset_dialog (login_window, dialog_mode); + } + +} + +static void +do_cancel (GdmGreeterLoginWindow *login_window) +{ + /* need to wait for response from backend */ + set_message (login_window, _("Cancelling…")); + restart_conversations (login_window); + reset_dialog_after_messages (login_window, MODE_SELECTION); +} + +gboolean +gdm_greeter_login_window_ready (GdmGreeterLoginWindow *login_window, + const char *service_name) +{ + GdmLoginExtension *extension; + + g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE); + + extension = find_extension_with_service_name (login_window, service_name); + + if (extension != NULL) { + if (!login_window->priv->user_chooser_loaded) { + g_debug ("GdmGreeterLoginWindow: Ignoring daemon Ready event since not loaded yet"); + login_window->priv->extensions_to_enable = g_list_prepend (login_window->priv->extensions_to_enable, + extension); + return TRUE; + } else if (login_window->priv->next_mode != MODE_UNDEFINED) { + g_debug ("GdmGreeterLoginWindow: Ignoring daemon Ready event since still showing messages"); + login_window->priv->extensions_to_enable = g_list_prepend (login_window->priv->extensions_to_enable, + extension); + return TRUE; + } + + gdm_login_extension_set_ready (extension); + } + + set_sensitive (GDM_GREETER_LOGIN_WINDOW (login_window), TRUE); + set_ready (GDM_GREETER_LOGIN_WINDOW (login_window)); + set_focus (GDM_GREETER_LOGIN_WINDOW (login_window)); + gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (login_window))); + + /* If the user list is disabled, then start the PAM conversation */ + if (can_jump_to_authenticate (login_window)) { + g_debug ("Starting PAM conversation since user list disabled or no local users"); + g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED], + 0, GDM_USER_CHOOSER_USER_OTHER); + begin_other_verification (login_window); + } + + return TRUE; +} + +static void +handle_stopped_conversation (GdmGreeterLoginWindow *login_window, + const char *service_name) +{ + GdmLoginExtension *extension; + + /* If the password conversation failed, then start over + * + * FIXME: we need to get this policy out of the source code + */ + if (strcmp (service_name, "gdm-password") == 0 || + strcmp (service_name, "gdm") == 0) { + g_debug ("GdmGreeterLoginWindow: main conversation failed, starting over"); + restart_conversations (login_window); + reset_dialog_after_messages (login_window, MODE_SELECTION); + return; + } + + if (login_window->priv->dialog_mode == MODE_AUTHENTICATION) { + g_debug ("GdmGreeterLoginWindow: conversation failed, starting over"); + restart_conversations (login_window); + reset_dialog_after_messages (login_window, MODE_AUTHENTICATION); + return; + } else if (login_window->priv->dialog_mode != MODE_MULTIPLE_AUTHENTICATION) { + g_warning ("conversation %s stopped when it shouldn't have been running (mode %d)", + service_name, login_window->priv->dialog_mode); + restart_conversations (login_window); + return; + } + + extension = find_extension_with_service_name (login_window, service_name); + + if (extension != NULL) { + gdm_login_extension_reset (extension); + + login_window->priv->extensions_to_stop = g_list_remove (login_window->priv->extensions_to_stop, extension); + } + + /* If every conversation has failed, then just start over. + */ + extension = gdm_extension_list_get_active_extension (GDM_EXTENSION_LIST (login_window->priv->extension_list)); + + if (extension == NULL || !gdm_login_extension_is_enabled (extension)) { + g_debug ("GdmGreeterLoginWindow: No conversations left, starting over"); + restart_conversations (login_window); + reset_dialog_after_messages (login_window, MODE_SELECTION); + } + + if (extension != NULL) { + g_object_unref (extension); + } + + update_extension_list_visibility (login_window); +} + +gboolean +gdm_greeter_login_window_conversation_stopped (GdmGreeterLoginWindow *login_window, + const char *service_name) +{ + GdmLoginExtension *extension; + + g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE); + + g_debug ("GdmGreeterLoginWindow: conversation '%s' has stopped", service_name); + + extension = find_extension_with_service_name (login_window, service_name); + + if (extension != NULL && gdm_login_extension_is_enabled (extension)) { + if (gdm_login_extension_has_queued_messages (extension)) { + login_window->priv->extensions_to_stop = g_list_prepend (login_window->priv->extensions_to_stop, extension); + } else { + handle_stopped_conversation (login_window, service_name); + } + } + + return TRUE; +} + +static gboolean +restart_extension_conversation (GdmLoginExtension *extension, + GdmGreeterLoginWindow *login_window) +{ + char *service_name; + + login_window->priv->extensions_to_stop = g_list_remove (login_window->priv->extensions_to_stop, extension); + + service_name = gdm_login_extension_get_service_name (extension); + if (service_name != NULL) { + char *name; + + name = gdm_login_extension_get_name (extension); + g_debug ("GdmGreeterLoginWindow: restarting '%s' conversation", name); + g_free (name); + + g_signal_emit (login_window, signals[START_CONVERSATION], 0, service_name); + g_free (service_name); + } + + return FALSE; +} + +gboolean +gdm_greeter_login_window_reset (GdmGreeterLoginWindow *login_window) +{ + g_debug ("GdmGreeterLoginWindow: window reset"); + + g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE); + + reset_dialog_after_messages (login_window, MODE_SELECTION); + g_list_foreach (login_window->priv->extensions, + (GFunc) restart_extension_conversation, + login_window); + + g_free (login_window->priv->service_name_of_session_ready_to_start); + login_window->priv->service_name_of_session_ready_to_start = NULL; + + return TRUE; +} + +gboolean +gdm_greeter_login_window_info (GdmGreeterLoginWindow *login_window, + const char *service_name, + const char *text) +{ + GdmLoginExtension *extension; + + g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE); + g_debug ("GdmGreeterLoginWindow: info: %s", text); + + maybe_show_cancel_button (login_window); + extension = find_extension_with_service_name (login_window, service_name); + + if (extension != NULL) { + gdm_login_extension_queue_message (extension, + GDM_SERVICE_MESSAGE_TYPE_INFO, + text); + show_extension_actions (extension); + } + + return TRUE; +} + +gboolean +gdm_greeter_login_window_problem (GdmGreeterLoginWindow *login_window, + const char *service_name, + const char *text) +{ + GdmLoginExtension *extension; + + g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE); + g_debug ("GdmGreeterLoginWindow: problem: %s", text); + maybe_show_cancel_button (login_window); + + extension = find_extension_with_service_name (login_window, service_name); + + if (extension != NULL) { + gdm_login_extension_queue_message (extension, + GDM_SERVICE_MESSAGE_TYPE_PROBLEM, + text); + show_extension_actions (extension); + } + + return TRUE; +} + +static void +request_timed_login (GdmGreeterLoginWindow *login_window) +{ + g_debug ("GdmGreeterLoginWindow: requesting timed login"); + + gtk_widget_show (login_window->priv->user_chooser); + + if (login_window->priv->dialog_mode != MODE_SELECTION) { + reset_dialog (login_window, MODE_SELECTION); + } + + if (!login_window->priv->timed_login_already_enabled) { + gdm_user_chooser_widget_set_chosen_user_name (GDM_USER_CHOOSER_WIDGET (login_window->priv->user_chooser), + GDM_USER_CHOOSER_USER_AUTO); + } + + login_window->priv->timed_login_already_enabled = TRUE; +} + +gboolean +gdm_greeter_login_window_service_unavailable (GdmGreeterLoginWindow *login_window, + const char *service_name) +{ + GdmLoginExtension *extension; + + g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE); + g_debug ("GdmGreeterLoginWindow: service unavailable: %s", service_name); + + extension = find_extension_with_service_name (login_window, service_name); + + if (extension != NULL) { + GdmLoginExtension *active_extension; + + gdm_login_extension_set_enabled (extension, FALSE); + + active_extension = gdm_extension_list_get_active_extension (GDM_EXTENSION_LIST (login_window->priv->extension_list)); + + if (active_extension == extension) { + restart_conversations (login_window); + } + + if (active_extension != NULL) { + g_object_unref (active_extension); + } + } + + return TRUE; +} + +void +gdm_greeter_login_window_request_timed_login (GdmGreeterLoginWindow *login_window, + const char *username, + int delay) +{ + g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window)); + + g_debug ("GdmGreeterLoginWindow: requested automatic login for user '%s' in %d seconds", username, delay); + + g_free (login_window->priv->timed_login_username); + login_window->priv->timed_login_username = g_strdup (username); + login_window->priv->timed_login_delay = delay; + + /* add the auto user right away so we won't trigger a mode + switch to authenticate when the user list is disabled */ + gdm_user_chooser_widget_set_show_user_auto (GDM_USER_CHOOSER_WIDGET (login_window->priv->user_chooser), TRUE); + + /* if the users aren't loaded then we'll handle it in when they are */ + if (login_window->priv->user_chooser_loaded) { + g_debug ("Handling timed login request since users are already loaded."); + request_timed_login (login_window); + } else { + g_debug ("Waiting to handle timed login request until users are loaded."); + } +} + +static void +gdm_greeter_login_window_start_session (GdmGreeterLoginWindow *login_window) +{ + g_debug ("GdmGreeterLoginWindow: starting session"); + g_signal_emit (login_window, + signals[START_SESSION], + 0, + login_window->priv->service_name_of_session_ready_to_start); + g_free (login_window->priv->service_name_of_session_ready_to_start); + login_window->priv->service_name_of_session_ready_to_start = NULL; +} + +static void +gdm_greeter_login_window_start_session_when_ready (GdmGreeterLoginWindow *login_window, + const char *service_name) +{ + GdmLoginExtension *extension; + + extension = find_extension_with_service_name (login_window, service_name); + + login_window->priv->service_name_of_session_ready_to_start = g_strdup (service_name); + + if (!gdm_login_extension_has_queued_messages (extension)) { + g_debug ("GdmGreeterLoginWindow: starting session"); + g_signal_emit (login_window, signals[START_SESSION], 0, service_name); + gdm_greeter_login_window_start_session (login_window); + } +} + +gboolean +gdm_greeter_login_window_info_query (GdmGreeterLoginWindow *login_window, + const char *service_name, + const char *text) +{ + GdmLoginExtension *extension; + + g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE); + + login_window->priv->num_queries++; + maybe_show_cancel_button (login_window); + + g_debug ("GdmGreeterLoginWindow: info query: %s", text); + + extension = find_extension_with_service_name (login_window, service_name); + + if (extension != NULL) { + gdm_login_extension_ask_question (extension, text); + } + + set_log_in_button_mode (login_window, LOGIN_BUTTON_ANSWER_QUERY); + set_sensitive (GDM_GREETER_LOGIN_WINDOW (login_window), TRUE); + set_ready (GDM_GREETER_LOGIN_WINDOW (login_window)); + set_focus (GDM_GREETER_LOGIN_WINDOW (login_window)); + + gdm_chooser_widget_propagate_pending_key_events (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser)); + + return TRUE; +} + +gboolean +gdm_greeter_login_window_secret_info_query (GdmGreeterLoginWindow *login_window, + const char *service_name, + const char *text) +{ + + GdmLoginExtension *extension; + + g_return_val_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window), FALSE); + + login_window->priv->num_queries++; + maybe_show_cancel_button (login_window); + + extension = find_extension_with_service_name (login_window, service_name); + + if (extension != NULL) { + gdm_login_extension_ask_secret (extension, text); + } + + set_log_in_button_mode (login_window, LOGIN_BUTTON_ANSWER_QUERY); + set_sensitive (GDM_GREETER_LOGIN_WINDOW (login_window), TRUE); + set_ready (GDM_GREETER_LOGIN_WINDOW (login_window)); + set_focus (GDM_GREETER_LOGIN_WINDOW (login_window)); + + gdm_chooser_widget_propagate_pending_key_events (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser)); + + return TRUE; +} + +void +gdm_greeter_login_window_session_opened (GdmGreeterLoginWindow *login_window, + const char *service_name) +{ + g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window)); + + g_debug ("GdmGreeterLoginWindow: session now opened via service %s", + service_name); + + gdm_greeter_login_window_start_session_when_ready (login_window, + service_name); +} + +static void +_gdm_greeter_login_window_set_display_is_local (GdmGreeterLoginWindow *login_window, + gboolean is) +{ + login_window->priv->display_is_local = is; +} + +static void +gdm_greeter_login_window_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmGreeterLoginWindow *self; + + self = GDM_GREETER_LOGIN_WINDOW (object); + + switch (prop_id) { + case PROP_DISPLAY_IS_LOCAL: + _gdm_greeter_login_window_set_display_is_local (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_greeter_login_window_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmGreeterLoginWindow *self; + + self = GDM_GREETER_LOGIN_WINDOW (object); + + switch (prop_id) { + case PROP_DISPLAY_IS_LOCAL: + g_value_set_boolean (value, self->priv->display_is_local); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cancel_button_clicked (GtkButton *button, + GdmGreeterLoginWindow *login_window) +{ + do_cancel (login_window); +} + +static void +on_user_chooser_visibility_changed (GdmGreeterLoginWindow *login_window) +{ + g_debug ("GdmGreeterLoginWindow: Chooser visibility changed"); + update_banner_message (login_window); +} + +static gboolean +begin_extension_verification_for_selected_user (GdmLoginExtension *extension, + GdmGreeterLoginWindow *login_window) +{ + char *user_name; + char *service_name; + + user_name = gdm_user_chooser_widget_get_chosen_user_name (GDM_USER_CHOOSER_WIDGET (login_window->priv->user_chooser)); + + if (user_name == NULL) { + return TRUE; + } + + service_name = gdm_login_extension_get_service_name (extension); + if (service_name != NULL) { + g_signal_emit (login_window, signals[BEGIN_VERIFICATION_FOR_USER], 0, service_name, user_name); + g_free (service_name); + } + + gdm_extension_list_add_extension (GDM_EXTENSION_LIST (login_window->priv->extension_list), + extension); + + g_free (user_name); + return FALSE; +} + +static void +enable_waiting_extensions (GdmGreeterLoginWindow *login_window) +{ + GList *node; + + node = login_window->priv->extensions_to_enable; + while (node != NULL) { + GdmLoginExtension *extension; + + extension = GDM_LOGIN_EXTENSION (node->data); + + gdm_login_extension_set_ready (extension); + + node = node->next; + } + + login_window->priv->extensions_to_enable = NULL; +} + +static void +on_users_loaded (GdmUserChooserWidget *user_chooser, + GdmGreeterLoginWindow *login_window) +{ + g_debug ("GdmGreeterLoginWindow: users loaded"); + login_window->priv->user_chooser_loaded = TRUE; + + update_banner_message (login_window); + + gtk_widget_show (login_window->priv->user_chooser); + + enable_waiting_extensions (login_window); + + if (login_window->priv->timed_login_username != NULL + && !login_window->priv->timed_login_already_enabled) { + request_timed_login (login_window); + } else if (can_jump_to_authenticate (login_window)) { + + gtk_widget_hide (login_window->priv->user_chooser); + + /* jump straight to authenticate */ + g_debug ("GdmGreeterLoginWindow: jumping straight to authenticate"); + g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED], + 0, GDM_USER_CHOOSER_USER_OTHER); + begin_other_verification (login_window); + } +} + +static void +choose_user (GdmGreeterLoginWindow *login_window, + const char *user_name) +{ + GdmLoginExtension *extension; + + g_assert (user_name != NULL); + g_debug ("GdmGreeterLoginWindow: user chosen '%s'", user_name); + + g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED], + 0, user_name); + + g_list_foreach (login_window->priv->extensions, + (GFunc) begin_extension_verification_for_selected_user, + login_window); + + extension = gdm_extension_list_get_active_extension (GDM_EXTENSION_LIST (login_window->priv->extension_list)); + set_extension_active (login_window, extension); + g_object_unref (extension); + + switch_mode (login_window, MODE_MULTIPLE_AUTHENTICATION); + update_extension_list_visibility (login_window); +} + +static void +begin_auto_login (GdmGreeterLoginWindow *login_window) +{ + g_signal_emit (login_window, signals[BEGIN_AUTO_LOGIN], 0, + login_window->priv->timed_login_username); + + login_window->priv->timed_login_enabled = TRUE; + restart_timed_login_timeout (login_window); + + /* just wait for the user to select language and stuff */ + set_message (login_window, _("Select language and click Log In")); + + clear_active_extension (login_window); + switch_mode (login_window, MODE_TIMED_LOGIN); + + show_widget (login_window, "conversation-list", FALSE); + g_list_foreach (login_window->priv->extensions, + (GFunc) reset_extension, + login_window); +} + +static void +reset_extension_if_not_given (GdmLoginExtension *extension, + GdmLoginExtension *given_extension) +{ + if (extension == given_extension) { + return; + } + + gdm_login_extension_reset (extension); +} + +static void +reset_every_extension_but_given_extension (GdmGreeterLoginWindow *login_window, + GdmLoginExtension *extension) +{ + g_list_foreach (login_window->priv->extensions, + (GFunc) reset_extension_if_not_given, + extension); + +} + +static void +begin_single_service_verification (GdmGreeterLoginWindow *login_window, + const char *service_name) +{ + GdmLoginExtension *extension; + + extension = find_extension_with_service_name (login_window, service_name); + + if (extension == NULL) { + g_debug ("GdmGreeterLoginWindow: %s has no extension associated with it", service_name); + return; + } + + g_debug ("GdmGreeterLoginWindow: Beginning %s auth conversation", service_name); + + /* FIXME: we should probably give the plugin more say for + * what happens here. + */ + g_signal_emit (login_window, signals[BEGIN_VERIFICATION], 0, service_name); + + reset_every_extension_but_given_extension (login_window, extension); + + set_extension_active (login_window, extension); + switch_mode (login_window, MODE_AUTHENTICATION); + + show_widget (login_window, "conversation-list", FALSE); +} + +static void +on_user_chooser_activated (GdmUserChooserWidget *user_chooser, + GdmGreeterLoginWindow *login_window) +{ + char *user_name; + char *item_id; + + user_name = gdm_user_chooser_widget_get_chosen_user_name (GDM_USER_CHOOSER_WIDGET (login_window->priv->user_chooser)); + + if (user_name != NULL) { + g_debug ("GdmGreeterLoginWindow: user chosen '%s'", user_name); + choose_user (login_window, user_name); + g_free (user_name); + return; + } + + item_id = gdm_chooser_widget_get_active_item (GDM_CHOOSER_WIDGET (user_chooser)); + g_debug ("GdmGreeterLoginWindow: item chosen '%s'", item_id); + + g_signal_emit (G_OBJECT (login_window), signals[USER_SELECTED], + 0, item_id); + + if (strcmp (item_id, GDM_USER_CHOOSER_USER_OTHER) == 0) { + g_debug ("GdmGreeterLoginWindow: Starting all auth conversations"); + g_free (item_id); + + begin_other_verification (login_window); + } else if (strcmp (item_id, GDM_USER_CHOOSER_USER_GUEST) == 0) { + /* FIXME: handle guest account stuff */ + g_free (item_id); + } else if (strcmp (item_id, GDM_USER_CHOOSER_USER_AUTO) == 0) { + g_debug ("GdmGreeterLoginWindow: Starting auto login"); + g_free (item_id); + + begin_auto_login (login_window); + } else { + g_debug ("GdmGreeterLoginWindow: Starting single auth conversation"); + begin_single_service_verification (login_window, item_id); + g_free (item_id); + } +} + +static void +on_user_unchosen (GdmUserChooserWidget *user_chooser, + GdmGreeterLoginWindow *login_window) +{ + do_cancel (login_window); +} + +static void +on_session_activated (GdmSessionOptionWidget *session_option_widget, + GdmGreeterLoginWindow *login_window) +{ + char *session; + + session = gdm_session_option_widget_get_current_session (GDM_SESSION_OPTION_WIDGET (login_window->priv->session_option_widget)); + if (session == NULL) { + return; + } + + g_signal_emit (login_window, signals[SESSION_SELECTED], 0, session); + + g_free (session); +} + +void +gdm_greeter_login_window_set_default_session_name (GdmGreeterLoginWindow *login_window, + const char *session_name) +{ + g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window)); + + if (session_name != NULL && !gdm_option_widget_lookup_item (GDM_OPTION_WIDGET (login_window->priv->session_option_widget), + session_name, NULL, NULL, NULL)) { + if (strcmp (session_name, GDM_CUSTOM_SESSION) == 0) { + gdm_option_widget_add_item (GDM_OPTION_WIDGET (login_window->priv->session_option_widget), + GDM_CUSTOM_SESSION, + C_("customsession", "Custom"), + _("Custom session"), + GDM_OPTION_WIDGET_POSITION_TOP); + } else { + g_warning ("Default session is not available"); + return; + } + } + + gdm_option_widget_set_default_item (GDM_OPTION_WIDGET (login_window->priv->session_option_widget), + session_name); +} + +static void +rotate_computer_info (GdmGreeterLoginWindow *login_window) +{ + GtkWidget *notebook; + int current_page; + int n_pages; + + /* switch page */ + notebook = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "computer-info-notebook")); + current_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook)); + n_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)); + + if (current_page + 1 < n_pages) { + gtk_notebook_next_page (GTK_NOTEBOOK (notebook)); + } else { + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), 0); + } + +} + +static gboolean +on_computer_info_label_button_press (GtkWidget *widget, + GdkEventButton *event, + GdmGreeterLoginWindow *login_window) +{ + rotate_computer_info (login_window); + return FALSE; +} + +static char * +file_read_one_line (const char *filename) +{ + FILE *f; + char *line; + char buf[4096]; + + line = NULL; + + f = fopen (filename, "r"); + if (f == NULL) { + g_warning ("Unable to open file %s: %s", filename, g_strerror (errno)); + goto out; + } + + if (fgets (buf, sizeof (buf), f) == NULL) { + g_warning ("Unable to read from file %s", filename); + goto out; + } + + line = g_strdup (buf); + g_strchomp (line); + + out: + fclose (f); + + return line; +} + +static const char *known_etc_info_files [] = { + "redhat-release", + "SuSE-release", + "gentoo-release", + "arch-release", + "debian_version", + "mandriva-release", + "slackware-version", + "system-release", + NULL +}; + + +static char * +get_system_version (void) +{ + char *version; + char *output; + int i; + + version = NULL; + + output = NULL; + if (g_spawn_command_line_sync (LSB_RELEASE_COMMAND, &output, NULL, NULL, NULL)) { + if (g_str_has_prefix (output, "Description:")) { + version = g_strdup (output + strlen ("Description:")); + } else { + version = g_strdup (output); + } + version = g_strstrip (version); + + /* lsb_release returns (none) if it doesn't know, + * so return NULL in that case */ + if (strcmp (version, "(none)") == 0) { + g_free (version); + version = NULL; + } + + g_free (output); + + goto out; + } + + for (i = 0; known_etc_info_files [i]; i++) { + char *path1; + char *path2; + + path1 = g_build_filename (SYSCONFDIR, known_etc_info_files [i], NULL); + path2 = g_build_filename ("/etc", known_etc_info_files [i], NULL); + if (g_access (path1, R_OK) == 0) { + version = file_read_one_line (path1); + } else if (g_access (path2, R_OK) == 0) { + version = file_read_one_line (path2); + } + g_free (path2); + g_free (path1); + if (version != NULL) { + break; + } + } + + if (version == NULL) { + output = NULL; + if (g_spawn_command_line_sync ("uname -sr", &output, NULL, NULL, NULL)) { + version = g_strchomp (output); + } + } + out: + return version; +} + +static void +create_computer_info (GdmGreeterLoginWindow *login_window) +{ + GtkWidget *label; + + gdm_profile_start (NULL); + + label = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "computer-info-name-label")); + if (label != NULL) { + char localhost[HOST_NAME_MAX + 1] = ""; + + if (gethostname (localhost, HOST_NAME_MAX) == 0) { + gtk_label_set_text (GTK_LABEL (label), localhost); + } + + /* If this isn't actually unique identifier for the computer, then + * don't bother showing it by default. + */ + if (strcmp (localhost, "localhost") == 0 || + strcmp (localhost, "localhost.localdomain") == 0) { + + rotate_computer_info (login_window); + } + } + + label = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "computer-info-version-label")); + if (label != NULL) { + char *version; + version = get_system_version (); + gtk_label_set_text (GTK_LABEL (label), version); + g_free (version); + } + + gdm_profile_end (NULL); +} + +#define INVISIBLE_CHAR_DEFAULT '*' +#define INVISIBLE_CHAR_BLACK_CIRCLE 0x25cf +#define INVISIBLE_CHAR_WHITE_BULLET 0x25e6 +#define INVISIBLE_CHAR_BULLET 0x2022 +#define INVISIBLE_CHAR_NONE 0 + +static void +on_extension_activated (GdmGreeterLoginWindow *login_window, + GdmLoginExtension *extension) +{ + set_extension_active (login_window, extension); +} + +static void +on_extension_deactivated (GdmGreeterLoginWindow *login_window, + GdmLoginExtension *extension) +{ + char *name; + + if (login_window->priv->active_extension != extension) { + g_warning ("inactive extension has been deactivated"); + return; + } + + name = gdm_login_extension_get_name (extension); + g_debug ("GdmGreeterLoginWindow: extension '%s' now in background", name); + g_free (name); + + clear_active_extension (login_window); + + login_window->priv->active_extension = gdm_extension_list_get_active_extension (GDM_EXTENSION_LIST (login_window->priv->extension_list)); + g_object_unref (login_window->priv->active_extension); +} + +static void +register_custom_types (GdmGreeterLoginWindow *login_window) +{ + GType types[] = { GDM_TYPE_USER_CHOOSER_WIDGET, + GDM_TYPE_SESSION_OPTION_WIDGET, + GDM_TYPE_EXTENSION_LIST }; + int i; + + for (i = 0; i < G_N_ELEMENTS (types); i++) { + g_debug ("Registering type '%s'", g_type_name (types[i])); + } +} + +static void +load_theme (GdmGreeterLoginWindow *login_window) +{ + GtkWidget *button; + GtkWidget *box; + GtkWidget *image; + GError* error = NULL; + + gdm_profile_start (NULL); + + register_custom_types (login_window); + + login_window->priv->builder = gtk_builder_new (); + if (!gtk_builder_add_from_file (login_window->priv->builder, UIDIR "/" UI_XML_FILE, &error)) { + g_warning ("Couldn't load builder file: %s", error->message); + g_error_free (error); + } + + g_assert (login_window->priv->builder != NULL); + + image = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "logo-image")); + if (image != NULL) { + GdkPixbuf *pixbuf; + char *path; + + path = g_settings_get_string (login_window->priv->settings, KEY_LOGO); + g_debug ("GdmGreeterLoginWindow: Got greeter logo '%s'", path); + + pixbuf = gdk_pixbuf_new_from_file_at_scale (path, -1, 48, TRUE, NULL); + g_free (path); + + if (pixbuf != NULL) { + gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf); + g_object_unref (pixbuf); + } + } + + box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "window-frame")); + gtk_container_add (GTK_CONTAINER (login_window), box); + gtk_widget_grab_default(GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, + "log-in-button"))); + + login_window->priv->user_chooser = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "user-chooser")); + + gdm_user_chooser_widget_set_show_only_chosen (GDM_USER_CHOOSER_WIDGET (login_window->priv->user_chooser), TRUE); + + g_signal_connect (login_window->priv->user_chooser, + "loaded", + G_CALLBACK (on_users_loaded), + login_window); + g_signal_connect (login_window->priv->user_chooser, + "activated", + G_CALLBACK (on_user_chooser_activated), + login_window); + g_signal_connect (login_window->priv->user_chooser, + "deactivated", + G_CALLBACK (on_user_unchosen), + login_window); + + g_signal_connect_swapped (login_window->priv->user_chooser, + "notify::list-visible", + G_CALLBACK (on_user_chooser_visibility_changed), + login_window); + + login_window->priv->session_option_widget = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "session-option-widget")); + + g_signal_connect (login_window->priv->session_option_widget, + "activated", + G_CALLBACK (on_session_activated), + login_window); + + login_window->priv->extension_list = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "extension-list")); + + g_signal_connect_swapped (GDM_EXTENSION_LIST (login_window->priv->extension_list), + "activated", + G_CALLBACK (on_extension_activated), + login_window); + g_signal_connect_swapped (GDM_EXTENSION_LIST (login_window->priv->extension_list), + "deactivated", + G_CALLBACK (on_extension_deactivated), + login_window); + + login_window->priv->auth_banner_label = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "auth-banner-label")); + /*make_label_small_italic (login_window->priv->auth_banner_label);*/ + login_window->priv->auth_page_box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "auth-page-box")); + + button = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "cancel-button")); + g_signal_connect (button, "clicked", G_CALLBACK (cancel_button_clicked), login_window); + + create_computer_info (login_window); + + box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "computer-info-event-box")); + g_signal_connect (box, "button-press-event", G_CALLBACK (on_computer_info_label_button_press), login_window); + + clear_active_extension (login_window); + switch_mode (login_window, MODE_SELECTION); + + gdm_profile_end (NULL); +} + +static gboolean +gdm_greeter_login_window_key_press_event (GtkWidget *widget, + GdkEventKey *event) +{ + GdmGreeterLoginWindow *login_window; + + login_window = GDM_GREETER_LOGIN_WINDOW (widget); + + if (event->keyval == GDK_KEY_Escape) { + if (login_window->priv->dialog_mode == MODE_AUTHENTICATION + || login_window->priv->dialog_mode == MODE_TIMED_LOGIN) { + do_cancel (GDM_GREETER_LOGIN_WINDOW (widget)); + } + } + + return GTK_WIDGET_CLASS (gdm_greeter_login_window_parent_class)->key_press_event (widget, event); +} + +static void +gdm_greeter_login_window_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + int monitor; + GdkScreen *screen; + GdkWindow *window; + GdkRectangle area; + GtkAllocation widget_allocation; + int min_size; + int nat_size; + + gtk_widget_get_preferred_width (gtk_bin_get_child (GTK_BIN (widget)), + &min_size, + &nat_size); + + /* Make width be at least 33% screen width */ + screen = gtk_widget_get_screen (widget); + window = gtk_widget_get_window (widget); + if (window == NULL) { + window = gdk_screen_get_root_window (screen); + } + monitor = gdk_screen_get_monitor_at_window (screen, window); + gdk_screen_get_monitor_geometry (screen, monitor, &area); + min_size = MAX (min_size, .33 * area.width); + nat_size = MAX (nat_size, .33 * area.width); + + /* Don't ever shrink window width */ + gtk_widget_get_allocation (widget, &widget_allocation); + + min_size = MAX (min_size, widget_allocation.width); + nat_size = MAX (nat_size, widget_allocation.width); + + if (minimum_size) + *minimum_size = min_size; + if (natural_size) + *natural_size = nat_size; +} + +static void +gdm_greeter_login_window_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + int monitor; + GdkScreen *screen; + GdkWindow *window; + GdkRectangle area; + int min_size; + int nat_size; + + gtk_widget_get_preferred_height (gtk_bin_get_child (GTK_BIN (widget)), + &min_size, + &nat_size); + + /* Make height be at most 80% of screen height */ + screen = gtk_widget_get_screen (widget); + window = gtk_widget_get_window (widget); + if (window == NULL) { + window = gdk_screen_get_root_window (screen); + } + monitor = gdk_screen_get_monitor_at_window (screen, window); + gdk_screen_get_monitor_geometry (screen, monitor, &area); + min_size = MIN (min_size, .8 * area.height); + nat_size = MIN (nat_size, .8 * area.height); + + if (minimum_size) + *minimum_size = min_size; + if (natural_size) + *natural_size = nat_size; +} + +static void +update_banner_message (GdmGreeterLoginWindow *login_window) +{ + gboolean enabled; + + if (login_window->priv->auth_banner_label == NULL) { + /* if the theme doesn't have a banner message */ + g_debug ("GdmGreeterLoginWindow: theme doesn't support a banner message"); + return; + } + + enabled = g_settings_get_boolean (login_window->priv->settings, KEY_BANNER_MESSAGE_ENABLED); + + login_window->priv->banner_message_enabled = enabled; + + if (! enabled) { + g_debug ("GdmGreeterLoginWindow: banner message disabled"); + gtk_widget_hide (login_window->priv->auth_banner_label); + } else { + char *message; + + message = g_settings_get_string (login_window->priv->settings, + KEY_BANNER_MESSAGE_TEXT); + + if (message != NULL) { + char *markup; + markup = g_markup_printf_escaped ("<small><i>%s</i></small>", message); + gtk_label_set_markup (GTK_LABEL (login_window->priv->auth_banner_label), + markup); + g_free (markup); + } + g_debug ("GdmGreeterLoginWindow: banner message: %s", message); + + gtk_widget_show (login_window->priv->auth_banner_label); + } +} + +static GObject * +gdm_greeter_login_window_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmGreeterLoginWindow *login_window; + + gdm_profile_start (NULL); + + login_window = GDM_GREETER_LOGIN_WINDOW (G_OBJECT_CLASS (gdm_greeter_login_window_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + + load_theme (login_window); + update_banner_message (login_window); + + gdm_profile_end (NULL); + + return G_OBJECT (login_window); +} + +static void +gdm_greeter_login_window_class_init (GdmGreeterLoginWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + object_class->get_property = gdm_greeter_login_window_get_property; + object_class->set_property = gdm_greeter_login_window_set_property; + object_class->constructor = gdm_greeter_login_window_constructor; + object_class->finalize = gdm_greeter_login_window_finalize; + + widget_class->key_press_event = gdm_greeter_login_window_key_press_event; + widget_class->get_preferred_width = gdm_greeter_login_window_get_preferred_width; + widget_class->get_preferred_height = gdm_greeter_login_window_get_preferred_height; + + gtk_container_class_handle_border_width (container_class); + + signals [START_CONVERSATION] = + g_signal_new ("start-conversation", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, start_conversation), + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + signals [BEGIN_AUTO_LOGIN] = + g_signal_new ("begin-auto-login", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, begin_auto_login), + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + signals [BEGIN_VERIFICATION] = + g_signal_new ("begin-verification", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, begin_verification), + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, G_TYPE_STRING); + signals [BEGIN_VERIFICATION_FOR_USER] = + g_signal_new ("begin-verification-for-user", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, begin_verification_for_user), + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 2, G_TYPE_STRING, G_TYPE_STRING); + signals [QUERY_ANSWER] = + g_signal_new ("query-answer", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, query_answer), + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 2, G_TYPE_STRING, G_TYPE_STRING); + signals [USER_SELECTED] = + g_signal_new ("user-selected", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, user_selected), + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, G_TYPE_STRING); + signals [SESSION_SELECTED] = + g_signal_new ("session-selected", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, session_selected), + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, G_TYPE_STRING); + signals [CANCELLED] = + g_signal_new ("cancelled", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, cancelled), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + signals [START_SESSION] = + g_signal_new ("start-session", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmGreeterLoginWindowClass, start_session), + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, G_TYPE_STRING); + + g_object_class_install_property (object_class, + PROP_DISPLAY_IS_LOCAL, + g_param_spec_boolean ("display-is-local", + "display is local", + "display is local", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_type_class_add_private (klass, sizeof (GdmGreeterLoginWindowPrivate)); +} + +static void +on_gsettings_key_changed (GSettings *settings, + gchar *key, + gpointer user_data) +{ + GdmGreeterLoginWindow *login_window; + + login_window = GDM_GREETER_LOGIN_WINDOW (user_data); + + if (strcmp (key, KEY_BANNER_MESSAGE_ENABLED) == 0) { + gboolean enabled; + + enabled = g_settings_get_boolean (settings, key); + + g_debug ("setting key %s = %d", key, enabled); + + login_window->priv->banner_message_enabled = enabled; + update_banner_message (login_window); + + } else if (strcmp (key, KEY_BANNER_MESSAGE_TEXT) == 0) { + if (login_window->priv->banner_message_enabled) { + update_banner_message (login_window); + } + } else { + g_debug ("GdmGreeterLoginWindow: Config key not handled: %s", key); + } +} + +static void +on_login_extension_answer (GdmGreeterLoginWindow *login_window, + const char *text, + GdmLoginExtension *extension) +{ + if (text != NULL) { + char *service_name; + + service_name = gdm_login_extension_get_service_name (extension); + if (service_name != NULL) { + g_signal_emit (login_window, signals[QUERY_ANSWER], 0, service_name, text); + g_free (service_name); + } + } + + set_sensitive (login_window, TRUE); + set_ready (login_window); +} + +static void +on_login_extension_cancel (GdmGreeterLoginWindow *login_window, + GdmLoginExtension *extension) +{ + restart_conversations (login_window); +} + +static gboolean +on_login_extension_chose_user (GdmGreeterLoginWindow *login_window, + const char *username, + GdmLoginExtension *extension) +{ + if (!login_window->priv->user_chooser_loaded) { + char *name; + + name = gdm_login_extension_get_name (extension); + g_warning ("Task %s is trying to choose user before list is loaded", name); + g_free (name); + return FALSE; + } + + /* If we're already authenticating then we can't pick a user + */ + if (login_window->priv->dialog_mode == MODE_AUTHENTICATION || login_window->priv->dialog_mode == MODE_MULTIPLE_AUTHENTICATION) { + return FALSE; + } + + gdm_user_chooser_widget_set_chosen_user_name (GDM_USER_CHOOSER_WIDGET (login_window->priv->user_chooser), + username); + + return TRUE; +} + +static void +on_login_extension_message_queue_empty (GdmGreeterLoginWindow *login_window, + GdmLoginExtension *extension) +{ + gboolean needs_to_be_stopped; + + needs_to_be_stopped = g_list_find (login_window->priv->extensions_to_stop, extension) != NULL; + + if (needs_to_be_stopped) { + char *service_name; + + service_name = gdm_login_extension_get_service_name (extension); + handle_stopped_conversation (login_window, service_name); + g_free (service_name); + } + + if (login_window->priv->service_name_of_session_ready_to_start != NULL) { + if (login_window->priv->active_extension == extension) { + gdm_greeter_login_window_start_session (login_window); + } + } else if (login_window->priv->next_mode != MODE_UNDEFINED) { + reset_dialog_after_messages (login_window, login_window->priv->next_mode); + } +} + +static void +on_button_action_label_changed (GtkWidget *button) +{ + GtkAction *action; + char *text; + + action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (button)); + + g_object_get (G_OBJECT (action), "label", &text, NULL); + + gtk_button_set_label (GTK_BUTTON (button), text); + g_free (text); +} + +static void +on_button_action_icon_name_changed (GtkWidget *button) +{ + GtkAction *action; + GtkWidget *image; + + action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (button)); + + if (gtk_action_get_is_important (action)) { + image = gtk_action_create_icon (GTK_ACTION (action), GTK_ICON_SIZE_BUTTON); + } else { + image = NULL; + } + + gtk_button_set_image (GTK_BUTTON (button), image); + +} + +static void +on_button_action_tooltip_changed (GtkWidget *button) +{ + GtkAction *action; + char *text; + + action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (button)); + + g_object_get (G_OBJECT (action), "tooltip", &text, NULL); + + gtk_widget_set_tooltip_text (button, text); + g_free (text); +} + +static GtkWidget * +create_button_from_action (GtkAction *action) +{ + GtkWidget *button; + + button = gtk_button_new (); + + gtk_activatable_set_related_action (GTK_ACTIVATABLE (button), action); + + g_signal_connect_swapped (action, + "notify::label", + G_CALLBACK (on_button_action_label_changed), + button); + g_signal_connect_swapped (action, + "notify::icon-name", + G_CALLBACK (on_button_action_icon_name_changed), + button); + g_signal_connect_swapped (action, + "notify::tooltip", + G_CALLBACK (on_button_action_tooltip_changed), + button); + + on_button_action_label_changed (button); + on_button_action_icon_name_changed (button); + on_button_action_tooltip_changed (button); + + if (strcmp (gtk_action_get_name (action), + GDM_LOGIN_EXTENSION_DEFAULT_ACTION) == 0) { + gtk_widget_set_can_default (button, TRUE); + } + + return button; +} + +static void +create_buttons_for_actions (GdmGreeterLoginWindow *login_window, + GtkActionGroup *actions) +{ + GList *action_list; + GList *node; + GtkWidget *box; + + action_list = gtk_action_group_list_actions (actions); + + box = GTK_WIDGET (gtk_builder_get_object (login_window->priv->builder, "buttonbox")); + for (node = action_list; node != NULL; node = node->next) { + GtkAction *action; + GtkWidget *button; + + action = node->data; + + button = create_button_from_action (action); + gtk_container_add (GTK_CONTAINER (box), button); + } + + g_list_free (action_list); +} + +static void +gdm_greeter_login_window_add_extension (GdmGreeterLoginWindow *login_window, + GdmLoginExtension *extension) +{ + char *name; + char *description; + char *service_name; + GtkActionGroup *actions; + + g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (login_window)); + g_return_if_fail (GDM_IS_LOGIN_EXTENSION (extension)); + + name = gdm_login_extension_get_name (extension); + description = gdm_login_extension_get_description (extension); + + if (!gdm_login_extension_is_visible (extension)) { + g_debug ("GdmGreeterLoginWindow: new extension '%s - %s' won't be added", + name, description); + g_free (name); + g_free (description); + return; + } + + actions = gdm_login_extension_get_actions (extension); + + create_buttons_for_actions (login_window, actions); + hide_extension_actions (extension); + + g_object_unref (actions); + + g_signal_connect_swapped (extension, + "answer", + G_CALLBACK (on_login_extension_answer), + login_window); + g_signal_connect_swapped (extension, + "cancel", + G_CALLBACK (on_login_extension_cancel), + login_window); + g_signal_connect_swapped (extension, + "user-chosen", + G_CALLBACK (on_login_extension_chose_user), + login_window); + g_signal_connect_swapped (extension, + "message-queue-empty", + G_CALLBACK (on_login_extension_message_queue_empty), + login_window); + + g_debug ("GdmGreeterLoginWindow: new extension '%s - %s' added", + name, description); + + login_window->priv->extensions = g_list_append (login_window->priv->extensions, extension); + service_name = gdm_login_extension_get_service_name (extension); + + if (gdm_login_extension_is_choosable (extension)) { + gdm_chooser_widget_add_item (GDM_CHOOSER_WIDGET (login_window->priv->user_chooser), + service_name, NULL, name, description, ~0, + FALSE, TRUE, NULL, NULL); + } + + g_free (name); + g_free (description); + + g_debug ("GdmGreeterLoginWindow: starting conversation with '%s'", service_name); + g_signal_emit (login_window, signals[START_CONVERSATION], 0, service_name); + g_free (service_name); +} + +static gboolean +on_window_state_event (GtkWidget *widget, + GdkEventWindowState *event, + gpointer data) +{ + if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) { + g_debug ("GdmGreeterLoginWindow: window iconified"); + gtk_window_deiconify (GTK_WINDOW (widget)); + } + + return FALSE; +} + +static gboolean +load_login_extensions (GdmGreeterLoginWindow *login_window) +{ + GList *extensions, *node; + GIOExtensionPoint *extension_point; + + g_debug ("GdmGreeterLoginWindow: loading extensions"); + + extension_point = g_io_extension_point_register (GDM_LOGIN_EXTENSION_POINT_NAME); + g_io_extension_point_set_required_type (extension_point, + GDM_TYPE_LOGIN_EXTENSION); + + g_io_modules_load_all_in_directory (GDM_SIMPLE_GREETER_PLUGINS_DIR); + + extensions = g_io_extension_point_get_extensions (extension_point); + + if (extensions == NULL) { + gdm_unified_extension_load (); + extensions = g_io_extension_point_get_extensions (extension_point); + } + + for (node = extensions; node != NULL; node = node->next) { + GIOExtension *extension; + GdmLoginExtension *login_extension; + + extension = (GIOExtension *) node->data; + + g_debug ("GdmGreeterLoginWindow: adding extension '%s'", + g_io_extension_get_name (extension)); + + login_extension = g_object_new (g_io_extension_get_type (extension), NULL); + + gdm_greeter_login_window_add_extension (GDM_GREETER_LOGIN_WINDOW (login_window), + login_extension); + } + + g_debug ("GdmGreeterLoginWindow: done loading extensions"); + + return FALSE; +} + +static void +gdm_greeter_login_window_init (GdmGreeterLoginWindow *login_window) +{ + GSettings *settings; + gboolean user_list_disable; + + gdm_profile_start (NULL); + + login_window->priv = GDM_GREETER_LOGIN_WINDOW_GET_PRIVATE (login_window); + login_window->priv->timed_login_enabled = FALSE; + login_window->priv->dialog_mode = MODE_UNDEFINED; + login_window->priv->next_mode = MODE_UNDEFINED; + + settings = g_settings_new (LOGIN_SCREEN_SCHEMA); + + /* The user list is not shown only if the user list is disabled and + * timed login is also not being used. + */ + user_list_disable = g_settings_get_boolean (settings, KEY_DISABLE_USER_LIST); + + login_window->priv->user_list_disabled = user_list_disable; + + gtk_window_set_title (GTK_WINDOW (login_window), _("Login Window")); + /*gtk_window_set_opacity (GTK_WINDOW (login_window), 0.85);*/ + gtk_window_set_position (GTK_WINDOW (login_window), GTK_WIN_POS_CENTER_ALWAYS); + gtk_window_set_deletable (GTK_WINDOW (login_window), FALSE); + gtk_window_set_decorated (GTK_WINDOW (login_window), FALSE); + gtk_window_set_keep_below (GTK_WINDOW (login_window), TRUE); + gtk_window_set_skip_taskbar_hint (GTK_WINDOW (login_window), TRUE); + gtk_window_set_skip_pager_hint (GTK_WINDOW (login_window), TRUE); + gtk_window_stick (GTK_WINDOW (login_window)); + gtk_container_set_border_width (GTK_CONTAINER (login_window), 0); + + g_signal_connect (login_window, + "window-state-event", + G_CALLBACK (on_window_state_event), + NULL); + + login_window->priv->settings = g_settings_new (LOGIN_SCREEN_SCHEMA); + + login_window->priv->gsettings_cnxn = g_signal_connect (login_window->priv->settings, + "changed", + G_CALLBACK (on_gsettings_key_changed), + login_window); + + g_idle_add ((GSourceFunc) load_login_extensions, login_window); + gdm_profile_end (NULL); +} + +static void +gdm_greeter_login_window_finalize (GObject *object) +{ + GdmGreeterLoginWindow *login_window; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_GREETER_LOGIN_WINDOW (object)); + + login_window = GDM_GREETER_LOGIN_WINDOW (object); + + g_return_if_fail (login_window->priv != NULL); + + if (login_window->priv->settings != NULL) { + g_object_unref (login_window->priv->settings); + } + + G_OBJECT_CLASS (gdm_greeter_login_window_parent_class)->finalize (object); +} + +GtkWidget * +gdm_greeter_login_window_new (gboolean is_local) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_GREETER_LOGIN_WINDOW, + "display-is-local", is_local, + "resizable", FALSE, + NULL); + + return GTK_WIDGET (object); +} diff --git a/gui/simple-greeter/gdm-greeter-login-window.h b/gui/simple-greeter/gdm-greeter-login-window.h new file mode 100644 index 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 <mccann@jhu.edu> + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef __GDM_GREETER_LOGIN_WINDOW_H +#define __GDM_GREETER_LOGIN_WINDOW_H + +#include <glib-object.h> +#include "gdm-login-extension.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_GREETER_LOGIN_WINDOW (gdm_greeter_login_window_get_type ()) +#define GDM_GREETER_LOGIN_WINDOW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_GREETER_LOGIN_WINDOW, GdmGreeterLoginWindow)) +#define GDM_GREETER_LOGIN_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_GREETER_LOGIN_WINDOW, GdmGreeterLoginWindowClass)) +#define GDM_IS_GREETER_LOGIN_WINDOW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_GREETER_LOGIN_WINDOW)) +#define GDM_IS_GREETER_LOGIN_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_GREETER_LOGIN_WINDOW)) +#define GDM_GREETER_LOGIN_WINDOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_GREETER_LOGIN_WINDOW, GdmGreeterLoginWindowClass)) + +typedef struct GdmGreeterLoginWindowPrivate GdmGreeterLoginWindowPrivate; + +typedef struct +{ + GtkWindow parent; + GdmGreeterLoginWindowPrivate *priv; +} GdmGreeterLoginWindow; + +typedef struct +{ + GtkWindowClass parent_class; + + /* signals */ + void (* start_conversation) (GdmGreeterLoginWindow *login_window, + const char *service_name); + void (* begin_auto_login) (GdmGreeterLoginWindow *login_window, + const char *username); + void (* begin_verification) (GdmGreeterLoginWindow *login_window, + const char *service_name); + void (* begin_verification_for_user) (GdmGreeterLoginWindow *login_window, + const char *service_name, + const char *username); + void (* query_answer) (GdmGreeterLoginWindow *login_window, + const char *service_name, + const char *text); + void (* user_selected) (GdmGreeterLoginWindow *login_window, + const char *text); + void (* session_selected) (GdmGreeterLoginWindow *login_window, + const char *text); + void (* cancelled) (GdmGreeterLoginWindow *login_window); + void (* start_session) (GdmGreeterLoginWindow *login_window); + +} GdmGreeterLoginWindowClass; + +GType gdm_greeter_login_window_get_type (void); +GtkWidget * gdm_greeter_login_window_new (gboolean display_is_local); + + +gboolean gdm_greeter_login_window_reset (GdmGreeterLoginWindow *login_window); +gboolean gdm_greeter_login_window_ready (GdmGreeterLoginWindow *login_window, + const char *service_name); +gboolean gdm_greeter_login_window_conversation_stopped (GdmGreeterLoginWindow *login_window, + const char *service_name); +gboolean gdm_greeter_login_window_info_query (GdmGreeterLoginWindow *login_window, + const char *service_name, + const char *text); +gboolean gdm_greeter_login_window_secret_info_query (GdmGreeterLoginWindow *login_window, + const char *service_name, + const char *text); +gboolean gdm_greeter_login_window_info (GdmGreeterLoginWindow *login_window, + const char *service_name, + const char *text); +gboolean gdm_greeter_login_window_problem (GdmGreeterLoginWindow *login_window, + const char *service_name, + const char *text); +void gdm_greeter_login_window_set_default_session_name (GdmGreeterLoginWindow *login_window, + const char *text); + +gboolean gdm_greeter_login_window_service_unavailable (GdmGreeterLoginWindow *login_window, + const char *service_name); + +void gdm_greeter_login_window_request_timed_login (GdmGreeterLoginWindow *login_window, + const char *username, + int delay); +void gdm_greeter_login_window_session_opened (GdmGreeterLoginWindow *login_window, + const char *service_name); + +G_END_DECLS + +#endif /* __GDM_GREETER_LOGIN_WINDOW_H */ diff --git a/gui/simple-greeter/gdm-greeter-login-window.ui b/gui/simple-greeter/gdm-greeter-login-window.ui new file mode 100644 index 00000000..163a8f47 --- /dev/null +++ b/gui/simple-greeter/gdm-greeter-login-window.ui @@ -0,0 +1,284 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.16"/> + <!-- interface-naming-policy toplevel-contextual --> + <object class="GtkFrame" id="window-frame"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">out</property> + <child> + <object class="GtkAlignment" id="alignment2"> + <property name="visible">True</property> + <property name="border_width">24</property> + <child> + <object class="GtkVBox" id="window-box"> + <property name="visible">True</property> + <property name="spacing">10</property> + <child> + <object class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <child> + <object class="GtkImage" id="logo-image"> + <property name="visible">True</property> + <property name="pixel_size">48</property> + <property name="icon_name">computer</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEventBox" id="computer-info-event-box"> + <property name="visible">True</property> + <property name="visible_window">False</property> + <child> + <object class="GtkNotebook" id="computer-info-notebook"> + <property name="visible">True</property> + <property name="show_tabs">False</property> + <property name="show_border">False</property> + <child> + <object class="GtkLabel" id="computer-info-name-label"> + <property name="visible">True</property> + <property name="label" translatable="yes">Computer Name</property> + </object> + </child> + <child type="tab"> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="label">page 5</property> + </object> + <packing> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="computer-info-version-label"> + <property name="visible">True</property> + <property name="label" translatable="yes">Version</property> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="label11"> + <property name="visible">True</property> + </object> + <packing> + <property name="position">1</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child type="tab"> + <object class="GtkLabel" id="label12"> + <property name="visible">True</property> + </object> + <packing> + <property name="position">2</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child type="tab"> + <object class="GtkLabel" id="label13"> + <property name="visible">True</property> + </object> + <packing> + <property name="position">3</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child type="tab"> + <object class="GtkLabel" id="label14"> + <property name="visible">True</property> + </object> + <packing> + <property name="position">4</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child type="tab"> + <object class="GtkLabel" id="label15"> + <property name="visible">True</property> + </object> + <packing> + <property name="position">5</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child type="tab"> + <object class="GtkLabel" id="label16"> + <property name="visible">True</property> + </object> + <packing> + <property name="position">6</property> + <property name="tab_fill">False</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="auth-banner-label"> + <property name="visible">True</property> + <property name="justify">center</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkAlignment" id="alignment3"> + <property name="visible">True</property> + <child> + <object class="GtkVBox" id="selection-box"> + <property name="visible">True</property> + <property name="spacing">2</property> + <child> + <object class="GtkAlignment" id="task-list-alignment"> + <property name="visible">True</property> + <property name="xalign">1.0</property> + <property name="xscale">0.0</property> + <child> + <object class="GdmExtensionList" id="extension-list"> + <property name="visible">False</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GdmUserChooserWidget" id="user-chooser"> + <property name="visible">False</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="auth-page-box"> + <property name="visible">True</property> + <property name="border_width">10</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkHButtonBox" id="buttonbox"> + <property name="visible">True</property> + <property name="spacing">6</property> + <property name="layout_style">end</property> + <child> + <object class="GdmSessionOptionWidget" id="session-option-widget"> + <property name="visible">False</property> + <property name="xscale">0.0</property> + <property name="yscale">0.0</property> + <property name="xalign">0.0</property> + <property name="yalign">1.0</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="secondary">True</property> + </packing> + </child> + <child> + <object class="GtkButton" id="cancel-button"> + <property name="label" translatable="yes">Cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="unlock-button"> + <property name="label" translatable="yes">Unlock</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="log-in-button"> + <property name="label" translatable="yes">Login</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">False</property> + <property name="receives_default">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">5</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </object> +</interface> diff --git a/gui/simple-greeter/gdm-greeter-panel.c b/gui/simple-greeter/gdm-greeter-panel.c new file mode 100644 index 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 <mccann@jhu.edu> + * Copyright (C) 2011 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <math.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/time.h> +#include <unistd.h> +#include <string.h> + +#ifdef ENABLE_RBAC_SHUTDOWN +#include <auth_attr.h> +#include <secdb.h> +#endif + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <gio/gio.h> +#include <gdk/gdk.h> +#include <gtk/gtk.h> + +#ifdef HAVE_UPOWER +#include <upower.h> +#endif + +#include "gdm-greeter-panel.h" +#include "gdm-clock-widget.h" +#include "gdm-timer.h" +#include "gdm-profile.h" +#include "gdm-common.h" + +#define CK_NAME "org.freedesktop.ConsoleKit" +#define CK_MANAGER_PATH "/org/freedesktop/ConsoleKit/Manager" +#define CK_MANAGER_INTERFACE "org.freedesktop.ConsoleKit.Manager" + +#define LOGIN1_NAME "org.freedesktop.login1" +#define LOGIN1_PATH "/org/freedesktop/login1" +#define LOGIN1_INTERFACE "org.freedesktop.login1.Manager" + +#define GPM_DBUS_NAME "org.gnome.SettingsDaemon" +#define GPM_DBUS_PATH "/org/gnome/SettingsDaemon/Power" +#define GPM_DBUS_INTERFACE "org.gnome.SettingsDaemon.Power" + +#define LOGIN_SCREEN_SCHEMA "org.gnome.login-screen" + +#define KEY_DISABLE_RESTART_BUTTONS "disable-restart-buttons" + +#define KEY_NOTIFICATION_AREA_PADDING "/apps/notification_area_applet/prefs/padding" + +#define GDM_GREETER_PANEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_GREETER_PANEL, GdmGreeterPanelPrivate)) + +struct GdmGreeterPanelPrivate +{ + int monitor; + GdkRectangle geometry; + GtkWidget *hbox; + GtkWidget *left_hbox; + GtkWidget *right_hbox; + GtkWidget *alignment; + GtkWidget *hostname_label; + GtkWidget *clock; + GtkWidget *status_menubar; + GtkWidget *shutdown_menu; + + GdmTimer *animation_timer; + double progress; + + GtkWidget *power_image; + GtkWidget *power_menu_item; + GtkWidget *power_menubar_item; + GDBusProxy *power_proxy; + gulong power_proxy_signal_handler; + gulong power_proxy_properties_changed_handler; + + guint display_is_local : 1; +}; + +enum { + PROP_0, + PROP_MONITOR, + PROP_DISPLAY_IS_LOCAL +}; + +enum { + DISCONNECTED, + NUMBER_OF_SIGNALS +}; + +static guint signals [NUMBER_OF_SIGNALS] = { 0, }; + +static void gdm_greeter_panel_class_init (GdmGreeterPanelClass *klass); +static void gdm_greeter_panel_init (GdmGreeterPanel *greeter_panel); +static void gdm_greeter_panel_finalize (GObject *object); + +G_DEFINE_TYPE (GdmGreeterPanel, gdm_greeter_panel, GTK_TYPE_WINDOW) + +static void +gdm_greeter_panel_set_monitor (GdmGreeterPanel *panel, + int monitor) +{ + g_return_if_fail (GDM_IS_GREETER_PANEL (panel)); + + if (panel->priv->monitor == monitor) { + return; + } + + panel->priv->monitor = monitor; + + gtk_widget_queue_resize (GTK_WIDGET (panel)); + + g_object_notify (G_OBJECT (panel), "monitor"); +} + +static void +_gdm_greeter_panel_set_display_is_local (GdmGreeterPanel *panel, + gboolean is) +{ + if (panel->priv->display_is_local != is) { + panel->priv->display_is_local = is; + g_object_notify (G_OBJECT (panel), "display-is-local"); + } +} + +static void +gdm_greeter_panel_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmGreeterPanel *self; + + self = GDM_GREETER_PANEL (object); + + switch (prop_id) { + case PROP_MONITOR: + gdm_greeter_panel_set_monitor (self, g_value_get_int (value)); + break; + case PROP_DISPLAY_IS_LOCAL: + _gdm_greeter_panel_set_display_is_local (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_greeter_panel_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmGreeterPanel *self; + + self = GDM_GREETER_PANEL (object); + + switch (prop_id) { + case PROP_MONITOR: + g_value_set_int (value, self->priv->monitor); + break; + case PROP_DISPLAY_IS_LOCAL: + g_value_set_boolean (value, self->priv->display_is_local); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_greeter_panel_dispose (GObject *object) +{ + GdmGreeterPanel *panel; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_GREETER_PANEL (object)); + + panel = GDM_GREETER_PANEL (object); + + if (panel->priv->power_proxy != NULL) { + g_object_unref (panel->priv->power_proxy); + panel->priv->power_proxy = NULL; + } + + G_OBJECT_CLASS (gdm_greeter_panel_parent_class)->dispose (object); +} + +/* copied from panel-toplevel.c */ +static void +gdm_greeter_panel_move_resize_window (GdmGreeterPanel *panel, + gboolean move, + gboolean resize) +{ + GtkWidget *widget; + + widget = GTK_WIDGET (panel); + + g_assert (gtk_widget_get_realized (widget)); + + if (move && resize) { + gdk_window_move_resize (gtk_widget_get_window (widget), + panel->priv->geometry.x, + panel->priv->geometry.y, + panel->priv->geometry.width, + panel->priv->geometry.height); + } else if (move) { + gdk_window_move (gtk_widget_get_window (widget), + panel->priv->geometry.x, + panel->priv->geometry.y); + } else if (resize) { + gdk_window_resize (gtk_widget_get_window (widget), + panel->priv->geometry.width, + panel->priv->geometry.height); + } +} + +static void +on_screen_size_changed (GdkScreen *screen, + GdmGreeterPanel *panel) +{ + gtk_widget_queue_resize (GTK_WIDGET (panel)); +} + +static void +update_power_icon (GdmGreeterPanel *panel) +{ + GVariant *variant; + + g_assert (panel->priv->power_proxy != NULL); + + variant = g_dbus_proxy_get_cached_property (panel->priv->power_proxy, "Icon"); + if (variant == NULL) { + /* FIXME: use an indeterminant icon */ + return; + } + + if (g_variant_is_of_type (variant, G_VARIANT_TYPE ("s"))) { + const char *name; + + name = g_variant_get_string (variant, NULL); + + if (name != NULL && *name != '\0') { + GError *error; + GIcon *icon; + error = NULL; + icon = g_icon_new_for_string (name, &error); + if (icon != NULL) { + g_debug ("setting power icon %s", name); + gtk_image_set_from_gicon (GTK_IMAGE (panel->priv->power_image), + icon, + GTK_ICON_SIZE_MENU); + gtk_widget_show_all (panel->priv->power_menubar_item); + } else { + gtk_widget_hide (panel->priv->power_menubar_item); + } + } else { + gtk_widget_hide (panel->priv->power_menubar_item); + } + } + + g_variant_unref (variant); +} + +static void +update_power_menu (GdmGreeterPanel *panel) +{ + GVariant *variant; + + g_assert (panel->priv->power_proxy != NULL); + + variant = g_dbus_proxy_get_cached_property (panel->priv->power_proxy, "Tooltip"); + if (variant == NULL) { + /* FIXME: use an indeterminant message */ + return; + } + + if (g_variant_is_of_type (variant, G_VARIANT_TYPE ("s"))) { + const char *txt; + + txt = g_variant_get_string (variant, NULL); + if (txt != NULL) { + gtk_menu_item_set_label (GTK_MENU_ITEM (panel->priv->power_menu_item), txt); + } + } + + g_variant_unref (variant); +} + +static void +on_power_proxy_g_signal (GDBusProxy *proxy, + const char *sender_name, + const char *signal_name, + GVariant *parameters, + GdmGreeterPanel *panel) +{ + if (g_strcmp0 (signal_name, "Changed") == 0) { + //update_power_icon (panel); + } +} + +static void +on_power_proxy_g_properties_changed (GDBusProxy *proxy, + GVariant *changed_properties, + GStrv *invalidated_properties, + GdmGreeterPanel *panel) +{ + g_debug ("Got power properties changed"); + if (g_variant_n_children (changed_properties) > 0) { + GVariantIter iter; + GVariant *value; + char *key; + + g_variant_iter_init (&iter, changed_properties); + + while (g_variant_iter_loop (&iter, "{&sv}", &key, &value)) { + if (g_strcmp0 (key, "Icon") == 0) { + g_debug ("Got power Icon changed"); + update_power_icon (panel); + } else if (g_strcmp0 (key, "Tooltip") == 0) { + g_debug ("Got power tooltip changed"); + update_power_menu (panel); + } + } + } +} + +static void +gdm_greeter_panel_real_realize (GtkWidget *widget) +{ + GdmGreeterPanel *panel = GDM_GREETER_PANEL (widget); + + if (GTK_WIDGET_CLASS (gdm_greeter_panel_parent_class)->realize) { + GTK_WIDGET_CLASS (gdm_greeter_panel_parent_class)->realize (widget); + } + + gdk_window_set_geometry_hints (gtk_widget_get_window (widget), NULL, GDK_HINT_POS); + + gdm_greeter_panel_move_resize_window (GDM_GREETER_PANEL (widget), TRUE, TRUE); + + g_signal_connect (gtk_window_get_screen (GTK_WINDOW (widget)), + "size_changed", + G_CALLBACK (on_screen_size_changed), + widget); + + if (panel->priv->power_proxy != NULL) { + update_power_icon (panel); + update_power_menu (panel); + panel->priv->power_proxy_signal_handler = g_signal_connect (panel->priv->power_proxy, + "g-signal", + G_CALLBACK (on_power_proxy_g_signal), + panel); + panel->priv->power_proxy_properties_changed_handler = g_signal_connect (panel->priv->power_proxy, + "g-properties-changed", + G_CALLBACK (on_power_proxy_g_properties_changed), + panel); + } + +} + +static void +gdm_greeter_panel_real_unrealize (GtkWidget *widget) +{ + GdmGreeterPanel *panel = GDM_GREETER_PANEL (widget); + + g_signal_handlers_disconnect_by_func (gtk_window_get_screen (GTK_WINDOW (widget)), + on_screen_size_changed, + widget); + + if (panel->priv->power_proxy != NULL + && panel->priv->power_proxy_signal_handler != 0) { + g_signal_handler_disconnect (panel->priv->power_proxy, panel->priv->power_proxy_signal_handler); + g_signal_handler_disconnect (panel->priv->power_proxy, panel->priv->power_proxy_properties_changed_handler); + } + + if (GTK_WIDGET_CLASS (gdm_greeter_panel_parent_class)->unrealize) { + GTK_WIDGET_CLASS (gdm_greeter_panel_parent_class)->unrealize (widget); + } +} + +static void +set_struts (GdmGreeterPanel *panel, + int x, + int y, + int width, + int height) +{ + gulong data[12] = { 0, }; + + /* _NET_WM_STRUT_PARTIAL: CARDINAL[12]/32 + * + * 0: left 1: right 2: top 3: bottom + * 4: left_start_y 5: left_end_y 6: right_start_y 7: right_end_y + * 8: top_start_x 9: top_end_x 10: bottom_start_x 11: bottom_end_x + * + * Note: In xinerama use struts relative to combined screen dimensions, + * not just the current monitor. + */ + + /* top */ + data[2] = panel->priv->geometry.y + height; + /* top_start_x */ + data[8] = x; + /* top_end_x */ + data[9] = x + width; + +#if 0 + g_debug ("Setting strut: top=%lu top_start_x=%lu top_end_x=%lu", data[2], data[8], data[9]); +#endif + + gdk_error_trap_push (); + if (gtk_widget_get_window (GTK_WIDGET (panel)) != NULL) { + gdk_property_change (gtk_widget_get_window (GTK_WIDGET (panel)), + gdk_atom_intern ("_NET_WM_STRUT_PARTIAL", FALSE), + gdk_atom_intern ("CARDINAL", FALSE), + 32, + GDK_PROP_MODE_REPLACE, + (guchar *) &data, + 12); + + gdk_property_change (gtk_widget_get_window (GTK_WIDGET (panel)), + gdk_atom_intern ("_NET_WM_STRUT", FALSE), + gdk_atom_intern ("CARDINAL", FALSE), + 32, + GDK_PROP_MODE_REPLACE, + (guchar *) &data, + 4); + } + + gdk_error_trap_pop_ignored (); +} + +static void +update_struts (GdmGreeterPanel *panel) +{ + /* FIXME: assumes only one panel */ + set_struts (panel, + panel->priv->geometry.x, + panel->priv->geometry.y, + panel->priv->geometry.width, + panel->priv->geometry.height); +} + +static void +update_geometry (GdmGreeterPanel *panel, + GtkRequisition *requisition) +{ + GdkRectangle geometry; + + gdk_screen_get_monitor_geometry (gtk_window_get_screen (GTK_WINDOW (panel)), + panel->priv->monitor, + &geometry); + + panel->priv->geometry.width = geometry.width; + panel->priv->geometry.height = requisition->height + 2 * gtk_container_get_border_width (GTK_CONTAINER (panel)); + + panel->priv->geometry.x = geometry.x; + panel->priv->geometry.y = geometry.y - panel->priv->geometry.height + panel->priv->progress * panel->priv->geometry.height; + +#if 0 + panel->priv->geometry.y += 50; +#endif +#if 0 + g_debug ("Setting geometry x:%d y:%d w:%d h:%d", + panel->priv->geometry.x, + panel->priv->geometry.y, + panel->priv->geometry.width, + panel->priv->geometry.height); +#endif + + update_struts (panel); +} + +static void +gdm_greeter_panel_get_preferred_size (GtkWidget *widget, + GtkOrientation orientation, + gint *minimum_size, + gint *natural_size) +{ + GdmGreeterPanel *panel; + GtkBin *bin; + GtkWidget *child; + GdkRectangle old_geometry; + int position_changed = FALSE; + int size_changed = FALSE; + GtkRequisition minimum_req, natural_req; + + panel = GDM_GREETER_PANEL (widget); + bin = GTK_BIN (widget); + child = gtk_bin_get_child (bin); + + minimum_req.width = 0; + minimum_req.height = 0; + natural_req.width = minimum_req.width; + natural_req.height = minimum_req.height; + + if (child != NULL && gtk_widget_get_visible (child)) { + int min_child_width, nat_child_width; + int min_child_height, nat_child_height; + + gtk_widget_get_preferred_width (gtk_bin_get_child (bin), + &min_child_width, + &nat_child_width); + gtk_widget_get_preferred_height (gtk_bin_get_child (bin), + &min_child_height, + &nat_child_height); + + minimum_req.width += min_child_width; + natural_req.width += nat_child_width; + minimum_req.height += min_child_height; + natural_req.height += nat_child_height; + } + + old_geometry = panel->priv->geometry; + update_geometry (panel, &natural_req); + + if (!gtk_widget_get_realized (widget)) + goto out; + + if (old_geometry.width != panel->priv->geometry.width || + old_geometry.height != panel->priv->geometry.height) { + size_changed = TRUE; + } + + if (old_geometry.x != panel->priv->geometry.x || + old_geometry.y != panel->priv->geometry.y) { + position_changed = TRUE; + } + + gdm_greeter_panel_move_resize_window (panel, position_changed, size_changed); + + out: + + if (orientation == GTK_ORIENTATION_HORIZONTAL) { + if (minimum_size) + *minimum_size = panel->priv->geometry.width; + if (natural_size) + *natural_size = panel->priv->geometry.width; + } else { + if (minimum_size) + *minimum_size = panel->priv->geometry.height; + if (natural_size) + *natural_size = panel->priv->geometry.height; + } +} + +static void +gdm_greeter_panel_real_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + gdm_greeter_panel_get_preferred_size (widget, GTK_ORIENTATION_HORIZONTAL, minimum_size, natural_size); +} + +static void +gdm_greeter_panel_real_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + gdm_greeter_panel_get_preferred_size (widget, GTK_ORIENTATION_VERTICAL, minimum_size, natural_size); +} + +static void +gdm_greeter_panel_real_show (GtkWidget *widget) +{ + GdmGreeterPanel *panel; + GtkSettings *settings; + gboolean animations_are_enabled; + + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (widget)); + g_object_get (settings, "gtk-enable-animations", &animations_are_enabled, NULL); + + panel = GDM_GREETER_PANEL (widget); + + if (animations_are_enabled) { + gdm_timer_start (panel->priv->animation_timer, 1.0); + } else { + panel->priv->progress = 1.0; + } + + GTK_WIDGET_CLASS (gdm_greeter_panel_parent_class)->show (widget); +} + +static void +gdm_greeter_panel_real_hide (GtkWidget *widget) +{ + GdmGreeterPanel *panel; + + panel = GDM_GREETER_PANEL (widget); + + gdm_timer_stop (panel->priv->animation_timer); + panel->priv->progress = 0.0; + + GTK_WIDGET_CLASS (gdm_greeter_panel_parent_class)->hide (widget); +} + +static void +on_animation_tick (GdmGreeterPanel *panel, + double progress) +{ + panel->priv->progress = progress * log ((G_E - 1.0) * progress + 1.0); + + gtk_widget_queue_resize (GTK_WIDGET (panel)); +} + +static gboolean +try_system_stop (GDBusConnection *connection, + GError **error) +{ + GVariant *reply; + gboolean res; + GError *call_error; + + g_debug ("GdmGreeterPanel: trying to stop system"); + + call_error = NULL; + reply = g_dbus_connection_call_sync (connection, + LOGIN1_NAME, + LOGIN1_PATH, + LOGIN1_INTERFACE, + "PowerOff", + g_variant_new ("(b)", TRUE), + NULL, + G_DBUS_CALL_FLAGS_NONE, + INT_MAX, + NULL, + &call_error); + + if (reply == NULL && (g_error_matches (call_error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER) || + g_error_matches (call_error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))) { + g_clear_error (&call_error); + reply = g_dbus_connection_call_sync (connection, + CK_NAME, + CK_MANAGER_PATH, + CK_MANAGER_INTERFACE, + "Stop", + NULL, + NULL, + G_DBUS_CALL_FLAGS_NONE, + INT_MAX, + NULL, + &call_error); + } + + if (reply != NULL) { + res = TRUE; + g_variant_unref (reply); + } else { + g_propagate_error (error, call_error); + res = FALSE; + } + + return res; +} + +static gboolean +try_system_restart (GDBusConnection *connection, + GError **error) +{ + GVariant *reply; + gboolean res; + GError *call_error; + + g_debug ("GdmGreeterPanel: trying to restart system"); + + call_error = NULL; + reply = g_dbus_connection_call_sync (connection, + LOGIN1_NAME, + LOGIN1_PATH, + LOGIN1_INTERFACE, + "Reboot", + g_variant_new ("(b)", TRUE), + NULL, + G_DBUS_CALL_FLAGS_NONE, + INT_MAX, + NULL, + &call_error); + + if (reply == NULL && (g_error_matches (call_error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER)|| + g_error_matches (call_error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))) { + g_clear_error (&call_error); + reply = g_dbus_connection_call_sync (connection, + CK_NAME, + CK_MANAGER_PATH, + CK_MANAGER_INTERFACE, + "Restart", + NULL, + NULL, + G_DBUS_CALL_FLAGS_NONE, + INT_MAX, + NULL, + &call_error); + } + + if (reply != NULL) { + res = TRUE; + g_variant_unref (reply); + } else { + g_propagate_error (error, call_error); + res = FALSE; + } + + return res; +} + +static gboolean +can_suspend (void) +{ + gboolean ret = FALSE; + +#ifdef HAVE_UPOWER + UpClient *up_client; + + /* use UPower to get data */ + up_client = up_client_new (); + ret = up_client_get_can_suspend (up_client); + g_object_unref (up_client); +#endif + + return ret; +} + +static void +do_system_suspend (void) +{ +#ifdef HAVE_UPOWER + gboolean ret; + UpClient *up_client; + GError *error = NULL; + + /* use UPower to trigger suspend */ + up_client = up_client_new (); + ret = up_client_suspend_sync (up_client, NULL, &error); + if (!ret) { + g_warning ("Couldn't suspend: %s", error->message); + g_error_free (error); + return; + } + g_object_unref (up_client); +#endif +} + +static void +do_system_restart (void) +{ + gboolean res; + GError *error; + GDBusConnection *connection; + + error = NULL; + connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (connection == NULL) { + g_warning ("Unable to get system bus connection: %s", error->message); + g_error_free (error); + return; + } + + res = try_system_restart (connection, &error); + if (!res) { + g_debug ("GdmGreeterPanel: unable to restart system: %s", + error->message); + g_error_free (error); + } +} + +static void +do_system_stop (void) +{ + gboolean res; + GError *error; + GDBusConnection *connection; + + error = NULL; + connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (connection == NULL) { + g_warning ("Unable to get system bus connection: %s", error->message); + g_error_free (error); + return; + } + + res = try_system_stop (connection, &error); + if (!res) { + g_debug ("GdmGreeterPanel: unable to stop system: %s", + error->message); + g_error_free (error); + } +} + +static void +do_disconnect (GtkWidget *widget, + GdmGreeterPanel *panel) +{ + g_signal_emit (panel, signals[DISCONNECTED], 0); +} + +static gboolean +get_show_restart_buttons (GdmGreeterPanel *panel) +{ + gboolean show; + GSettings *settings; + + settings = g_settings_new (LOGIN_SCREEN_SCHEMA); + + show = ! g_settings_get_boolean (settings, KEY_DISABLE_RESTART_BUTTONS); + +#ifdef ENABLE_RBAC_SHUTDOWN + { + char *username; + + username = g_get_user_name (); + if (username == NULL || !chkauthattr (RBAC_SHUTDOWN_KEY, username)) { + show = FALSE; + g_debug ("GdmGreeterPanel: Not showing stop/restart buttons for user %s due to RBAC key %s", + username, RBAC_SHUTDOWN_KEY); + } else { + g_debug ("GdmGreeterPanel: Showing stop/restart buttons for user %s due to RBAC key %s", + username, RBAC_SHUTDOWN_KEY); + } + } +#endif + g_object_unref (settings); + + return show; +} + +static inline void +override_style (GtkWidget *widget) +{ + GtkCssProvider *provider; + GtkStyleContext *context; + GError *error; + + g_debug ("updating style"); + + context = gtk_widget_get_style_context (widget); + + provider = gtk_css_provider_new (); + gtk_style_context_add_provider (context, + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + error = NULL; + gtk_css_provider_load_from_data (provider, + "* {\n" + " background-color: black;\n" + " color: #ccc;\n" + " border-width: 0;\n" + "}\n" + "*:selected {\n" + " background-color: #666666;\n" + " color: white;\n" + "}\n" + ".menu,\n" + ".menubar,\n" + ".menu.check,\n" + ".menu.radio {\n" + " background-color: black;\n" + " color: #ccc;\n" + " border-style: none;\n" + "}\n" + ".menu:hover,\n" + ".menubar:hover,\n" + ".menu.check:hover,\n" + ".menu.radio:hover {\n" + " background-color: #666666;\n" + " color: #ccc;\n" + " border-style: none;\n" + "}\n" + "GtkLabel:selected {\n" + " background-color: black;\n" + " color: #ccc;\n" + "}\n" + "\n" + "GtkLabel:selected:focused {\n" + " background-color: black;\n" + " color: #ccc;\n" + "}\n" + "GtkMenuBar {\n" + " background-color: black;\n" + " background-image: none;\n" + " color: #ccc;\n" + " -GtkMenuBar-internal-padding: 0;\n" + " -GtkMenuBar-shadow-type: none;\n" + " border-width: 0;\n" + " border-style: none;\n" + "}\n" + "GtkMenuItem {\n" + " background-color: black;\n" + " color: #ccc;\n" + "}\n" + "GtkImage {\n" + " background-color: black;\n" + " color: #ccc;\n" + "}\n", + -1, + &error); + if (error != NULL) { + g_warning ("Error loading style data: %s", error->message); + g_error_free (error); + } +} + +static void +add_shutdown_menu (GdmGreeterPanel *panel) +{ + GtkWidget *item; + GtkWidget *menu_item; + GtkWidget *box; + GtkWidget *image; + GIcon *gicon; + + item = gtk_menu_item_new (); + override_style (item); + box = gtk_hbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (item), box); + gtk_menu_shell_append (GTK_MENU_SHELL (panel->priv->status_menubar), item); + image = gtk_image_new (); + override_style (image); + + gicon = g_themed_icon_new ("system-shutdown-symbolic"); + gtk_image_set_from_gicon (GTK_IMAGE (image), gicon, GTK_ICON_SIZE_MENU); + g_object_unref (gicon); + + gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0); + + panel->priv->shutdown_menu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), panel->priv->shutdown_menu); + + if (! panel->priv->display_is_local) { + menu_item = gtk_menu_item_new_with_label ("Disconnect"); + g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (do_disconnect), panel); + gtk_menu_shell_append (GTK_MENU_SHELL (panel->priv->shutdown_menu), menu_item); + } else if (get_show_restart_buttons (panel)) { + if (can_suspend ()) { + menu_item = gtk_menu_item_new_with_label (_("Suspend")); + g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (do_system_suspend), NULL); + gtk_menu_shell_append (GTK_MENU_SHELL (panel->priv->shutdown_menu), menu_item); + } + + menu_item = gtk_menu_item_new_with_label (_("Restart")); + g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (do_system_restart), NULL); + gtk_menu_shell_append (GTK_MENU_SHELL (panel->priv->shutdown_menu), menu_item); + + menu_item = gtk_menu_item_new_with_label (_("Shut Down")); + g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (do_system_stop), NULL); + gtk_menu_shell_append (GTK_MENU_SHELL (panel->priv->shutdown_menu), menu_item); + } + gtk_widget_show_all (item); +} + +static void +add_battery_menu (GdmGreeterPanel *panel) +{ + GtkWidget *item; + GtkWidget *box; + GtkWidget *menu; + GError *error; + GIcon *gicon; + + error = NULL; + panel->priv->power_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + GPM_DBUS_NAME, + GPM_DBUS_PATH, + GPM_DBUS_INTERFACE, + NULL, + &error); + if (panel->priv->power_proxy == NULL) { + g_warning ("Unable to connect to power manager: %s", error->message); + g_error_free (error); + return; + } + + item = gtk_menu_item_new (); + + override_style (item); + box = gtk_hbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (item), box); + gtk_menu_shell_prepend (GTK_MENU_SHELL (panel->priv->status_menubar), item); + panel->priv->power_image = gtk_image_new (); + override_style (panel->priv->power_image); + + gicon = g_themed_icon_new ("battery-caution-symbolic"); + gtk_image_set_from_gicon (GTK_IMAGE (panel->priv->power_image), gicon, GTK_ICON_SIZE_MENU); + g_object_unref (gicon); + + gtk_box_pack_start (GTK_BOX (box), panel->priv->power_image, FALSE, FALSE, 0); + + menu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu); + + panel->priv->power_menu_item = gtk_menu_item_new_with_label (_("Unknown time remaining")); + gtk_widget_set_sensitive (panel->priv->power_menu_item, FALSE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), panel->priv->power_menu_item); + panel->priv->power_menubar_item = item; +} + +static void +setup_panel (GdmGreeterPanel *panel) +{ + GtkSizeGroup *sg; + + gdm_profile_start (NULL); + + gtk_widget_set_can_focus (GTK_WIDGET (panel), TRUE); + + override_style (GTK_WIDGET (panel)); + + panel->priv->geometry.x = -1; + panel->priv->geometry.y = -1; + panel->priv->geometry.width = -1; + panel->priv->geometry.height = -1; + + gtk_window_set_title (GTK_WINDOW (panel), _("Panel")); + gtk_window_set_decorated (GTK_WINDOW (panel), FALSE); + gtk_window_set_has_resize_grip (GTK_WINDOW (panel), FALSE); + + gtk_window_set_keep_above (GTK_WINDOW (panel), TRUE); + gtk_window_set_type_hint (GTK_WINDOW (panel), GDK_WINDOW_TYPE_HINT_DOCK); + + panel->priv->hbox = gtk_hbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (panel->priv->hbox), 0); + gtk_widget_show (panel->priv->hbox); + gtk_container_add (GTK_CONTAINER (panel), panel->priv->hbox); + + panel->priv->left_hbox = gtk_hbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (panel->priv->left_hbox), 0); + gtk_widget_show (panel->priv->left_hbox); + gtk_box_pack_start (GTK_BOX (panel->priv->hbox), panel->priv->left_hbox, TRUE, TRUE, 0); + + panel->priv->alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); + gtk_box_pack_start (GTK_BOX (panel->priv->hbox), panel->priv->alignment, FALSE, FALSE, 0); + gtk_widget_show (panel->priv->alignment); + + panel->priv->right_hbox = gtk_hbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (panel->priv->right_hbox), 0); + gtk_widget_show (panel->priv->right_hbox); + gtk_box_pack_start (GTK_BOX (panel->priv->hbox), panel->priv->right_hbox, TRUE, TRUE, 0); + + panel->priv->clock = gdm_clock_widget_new (); + gtk_widget_show (panel->priv->clock); + gtk_container_add (GTK_CONTAINER (panel->priv->alignment), panel->priv->clock); + + sg = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); + gtk_size_group_add_widget (sg, panel->priv->left_hbox); + gtk_size_group_add_widget (sg, panel->priv->right_hbox); + + panel->priv->status_menubar = gtk_menu_bar_new (); + override_style (panel->priv->status_menubar); + gtk_widget_show (panel->priv->status_menubar); + gtk_box_pack_end (GTK_BOX (panel->priv->right_hbox), GTK_WIDGET (panel->priv->status_menubar), FALSE, FALSE, 0); + + if (!panel->priv->display_is_local || get_show_restart_buttons (panel)) { + add_shutdown_menu (panel); + } + + add_battery_menu (panel); + + /* FIXME: we should only show hostname on panel when connected + to a remote host */ + if (0) { + panel->priv->hostname_label = gtk_label_new (g_get_host_name ()); + gtk_box_pack_start (GTK_BOX (panel->priv->hbox), panel->priv->hostname_label, FALSE, FALSE, 6); + gtk_widget_show (panel->priv->hostname_label); + } + + panel->priv->progress = 0.0; + panel->priv->animation_timer = gdm_timer_new (); + g_signal_connect_swapped (panel->priv->animation_timer, + "tick", + G_CALLBACK (on_animation_tick), + panel); + + gdm_profile_end (NULL); +} + +static GObject * +gdm_greeter_panel_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmGreeterPanel *greeter_panel; + + gdm_profile_start (NULL); + + greeter_panel = GDM_GREETER_PANEL (G_OBJECT_CLASS (gdm_greeter_panel_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + setup_panel (greeter_panel); + + gdm_profile_end (NULL); + + return G_OBJECT (greeter_panel); +} + +static void +gdm_greeter_panel_init (GdmGreeterPanel *panel) +{ + panel->priv = GDM_GREETER_PANEL_GET_PRIVATE (panel); + +} + +static void +gdm_greeter_panel_finalize (GObject *object) +{ + GdmGreeterPanel *greeter_panel; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_GREETER_PANEL (object)); + + greeter_panel = GDM_GREETER_PANEL (object); + + g_return_if_fail (greeter_panel->priv != NULL); + + g_signal_handlers_disconnect_by_func (object, on_animation_tick, greeter_panel); + g_object_unref (greeter_panel->priv->animation_timer); + + G_OBJECT_CLASS (gdm_greeter_panel_parent_class)->finalize (object); +} + +static void +gdm_greeter_panel_class_init (GdmGreeterPanelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = gdm_greeter_panel_get_property; + object_class->set_property = gdm_greeter_panel_set_property; + object_class->constructor = gdm_greeter_panel_constructor; + object_class->dispose = gdm_greeter_panel_dispose; + object_class->finalize = gdm_greeter_panel_finalize; + + widget_class->realize = gdm_greeter_panel_real_realize; + widget_class->unrealize = gdm_greeter_panel_real_unrealize; + widget_class->get_preferred_width = gdm_greeter_panel_real_get_preferred_width; + widget_class->get_preferred_height = gdm_greeter_panel_real_get_preferred_height; + widget_class->show = gdm_greeter_panel_real_show; + widget_class->hide = gdm_greeter_panel_real_hide; + + g_object_class_install_property (object_class, + PROP_MONITOR, + g_param_spec_int ("monitor", + "Xinerama monitor", + "The monitor (in terms of Xinerama) which the window is on", + 0, + G_MAXINT, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_DISPLAY_IS_LOCAL, + g_param_spec_boolean ("display-is-local", + "display is local", + "display is local", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + signals [DISCONNECTED] = + g_signal_new ("disconnected", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmGreeterPanelClass, disconnected), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + g_type_class_add_private (klass, sizeof (GdmGreeterPanelPrivate)); +} + +GtkWidget * +gdm_greeter_panel_new (GdkScreen *screen, + int monitor, + gboolean is_local) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_GREETER_PANEL, + "screen", screen, + "monitor", monitor, + "display-is-local", is_local, + NULL); + + return GTK_WIDGET (object); +} diff --git a/gui/simple-greeter/gdm-greeter-panel.h b/gui/simple-greeter/gdm-greeter-panel.h new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef __GDM_GREETER_PANEL_H +#define __GDM_GREETER_PANEL_H + +#include <glib-object.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_GREETER_PANEL (gdm_greeter_panel_get_type ()) +#define GDM_GREETER_PANEL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_GREETER_PANEL, GdmGreeterPanel)) +#define GDM_GREETER_PANEL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_GREETER_PANEL, GdmGreeterPanelClass)) +#define GDM_IS_GREETER_PANEL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_GREETER_PANEL)) +#define GDM_IS_GREETER_PANEL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_GREETER_PANEL)) +#define GDM_GREETER_PANEL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_GREETER_PANEL, GdmGreeterPanelClass)) + +typedef struct GdmGreeterPanelPrivate GdmGreeterPanelPrivate; + +typedef struct +{ + GtkWindow parent; + GdmGreeterPanelPrivate *priv; +} GdmGreeterPanel; + +typedef struct +{ + GtkWindowClass parent_class; + + void (* language_selected) (GdmGreeterPanel *panel, + const char *text); + + void (* session_selected) (GdmGreeterPanel *panel, + const char *text); + void (* disconnected) (GdmGreeterPanel *panel); +} GdmGreeterPanelClass; + +GType gdm_greeter_panel_get_type (void); + +GtkWidget * gdm_greeter_panel_new (GdkScreen *screen, + int monitor, + gboolean is_local); + +void gdm_greeter_panel_show_user_options (GdmGreeterPanel *panel); +void gdm_greeter_panel_hide_user_options (GdmGreeterPanel *panel); +void gdm_greeter_panel_reset (GdmGreeterPanel *panel); + +void gdm_greeter_panel_set_default_language_name (GdmGreeterPanel *panel, + const char *language_name); +void gdm_greeter_panel_set_default_session_name (GdmGreeterPanel *panel, + const char *session_name); +G_END_DECLS + +#endif /* __GDM_GREETER_PANEL_H */ diff --git a/gui/simple-greeter/gdm-greeter-session.c b/gui/simple-greeter/gdm-greeter-session.c new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> + +#include "gdm-client.h" + +#include "gdm-greeter-session.h" +#include "gdm-greeter-panel.h" +#include "gdm-greeter-login-window.h" +#include "gdm-user-chooser-widget.h" + +#include "gdm-profile.h" + +#define GDM_GREETER_SESSION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_GREETER_SESSION, GdmGreeterSessionPrivate)) + +#define MAX_LOGIN_TRIES 3 + +struct GdmGreeterSessionPrivate +{ + GdmClient *client; + GdmUserVerifier *user_verifier; + GdmRemoteGreeter *remote_greeter; + GdmGreeter *greeter; + + + GtkWidget *login_window; + GtkWidget *panel; + + guint num_tries; +}; + +enum { + PROP_0, +}; + +static void gdm_greeter_session_class_init (GdmGreeterSessionClass *klass); +static void gdm_greeter_session_init (GdmGreeterSession *greeter_session); +static void gdm_greeter_session_finalize (GObject *object); + +G_DEFINE_TYPE (GdmGreeterSession, gdm_greeter_session, G_TYPE_OBJECT) + +static gpointer session_object = NULL; + +static void +on_info (GdmClient *client, + const char *service_name, + const char *text, + GdmGreeterSession *session) +{ + g_debug ("GdmGreeterSession: Info: %s", text); + + gdm_greeter_login_window_info (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), service_name, text); +} + +static void +on_problem (GdmClient *client, + const char *service_name, + const char *text, + GdmGreeterSession *session) +{ + g_debug ("GdmGreeterSession: Problem: %s", text); + + gdm_greeter_login_window_problem (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), service_name, text); +} + +static void +on_service_unavailable (GdmClient *client, + const char *service_name, + GdmGreeterSession *session) +{ + g_debug ("GdmGreeterSession: Service Unavailable: %s", service_name); + + gdm_greeter_login_window_service_unavailable (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), service_name); +} + +static void +on_conversation_started (GdmClient *client, + const char *service_name, + GdmGreeterSession *session) +{ + g_debug ("GdmGreeterSession: Ready"); + + gdm_greeter_login_window_ready (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), + service_name); +} + +static void +on_conversation_stopped (GdmClient *client, + const char *service_name, + GdmGreeterSession *session) +{ + g_debug ("GdmGreeterSession: Conversation '%s' stopped", service_name); + + gdm_greeter_login_window_conversation_stopped (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), + service_name); +} + +static void +on_reset (GdmClient *client, + GdmGreeterSession *session) +{ + g_debug ("GdmGreeterSession: Reset"); + + session->priv->num_tries = 0; + + gdm_greeter_login_window_reset (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window)); +} + +static void +show_or_hide_user_options (GdmGreeterSession *session, + const char *username) +{ + if (username != NULL && strcmp (username, GDM_USER_CHOOSER_USER_OTHER) != 0) { + //gdm_greeter_panel_show_user_options (GDM_GREETER_PANEL (session->priv->panel)); + } else { + //gdm_greeter_panel_hide_user_options (GDM_GREETER_PANEL (session->priv->panel)); + } +} + +static void +on_selected_user_changed (GdmClient *client, + const char *text, + GdmGreeterSession *session) +{ + g_debug ("GdmGreeterSession: selected user changed: %s", text); + show_or_hide_user_options (session, text); +} + +static void +on_default_language_name_changed (GdmClient *client, + const char *text, + GdmGreeterSession *session) +{ + g_debug ("GdmGreeterSession: default language name changed: %s", text); +} + +static void +on_default_session_name_changed (GdmClient *client, + const char *text, + GdmGreeterSession *session) +{ + g_debug ("GdmGreeterSession: default session name changed: %s", text); + gdm_greeter_login_window_set_default_session_name (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), text); +} + +static void +on_timed_login_requested (GdmClient *client, + const char *text, + int delay, + GdmGreeterSession *session) +{ + g_debug ("GdmGreeterSession: timed login requested for user %s (in %d seconds)", text, delay); + gdm_greeter_login_window_request_timed_login (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), text, delay); +} + +static void +on_session_opened (GdmClient *client, + const char *service_name, + GdmGreeterSession *session) +{ + g_debug ("GdmGreeterSession: session opened"); + gdm_greeter_login_window_session_opened (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), service_name); +} + +static void +on_info_query (GdmClient *client, + const char *service_name, + const char *text, + GdmGreeterSession *session) +{ + g_debug ("GdmGreeterSession: Info query: %s", text); + + gdm_greeter_login_window_info_query (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), service_name, text); +} + +static void +on_secret_info_query (GdmClient *client, + const char *service_name, + const char *text, + GdmGreeterSession *session) +{ + g_debug ("GdmGreeterSession: Secret info query: %s", text); + + gdm_greeter_login_window_secret_info_query (GDM_GREETER_LOGIN_WINDOW (session->priv->login_window), service_name, text); +} + +static void +on_begin_auto_login (GdmGreeterLoginWindow *login_window, + const char *username, + GdmGreeterSession *session) +{ + gdm_greeter_call_begin_auto_login_sync (session->priv->greeter, + username, + NULL, + NULL); +} + +static void +get_user_verifier (GdmGreeterSession *session, + const char *username) +{ + GError *error = NULL; + + g_clear_object (&session->priv->user_verifier); + + if (username != NULL) { + session->priv->user_verifier = gdm_client_open_reauthentication_channel_sync (session->priv->client, + username, + NULL, + &error); + + if (error != NULL) { + g_debug ("GdmGreeterSession: could not get reauthentication channel for user %s: %s", username, error->message); + g_clear_error (&error); + } + } + + if (session->priv->user_verifier == NULL) { + session->priv->user_verifier = gdm_client_get_user_verifier_sync (session->priv->client, + NULL, + &error); + + if (error != NULL) { + g_debug ("GdmGreeterSession: could not get user verifier %s", error->message); + g_clear_error (&error); + } + + if (session->priv->user_verifier == NULL) { + + return; + } + } + g_signal_connect (session->priv->user_verifier, + "info-query", + G_CALLBACK (on_info_query), + session); + g_signal_connect (session->priv->user_verifier, + "secret-info-query", + G_CALLBACK (on_secret_info_query), + session); + g_signal_connect (session->priv->user_verifier, + "info", + G_CALLBACK (on_info), + session); + g_signal_connect (session->priv->user_verifier, + "problem", + G_CALLBACK (on_problem), + session); + g_signal_connect (session->priv->user_verifier, + "service-unavailable", + G_CALLBACK (on_service_unavailable), + session); + g_signal_connect (session->priv->user_verifier, + "conversation-started", + G_CALLBACK (on_conversation_started), + session); + g_signal_connect (session->priv->user_verifier, + "conversation-stopped", + G_CALLBACK (on_conversation_stopped), + session); + g_signal_connect (session->priv->user_verifier, + "reset", + G_CALLBACK (on_reset), + session); + g_signal_connect (session->priv->greeter, + "selected-user-changed", + G_CALLBACK (on_selected_user_changed), + session); + g_signal_connect (session->priv->greeter, + "default-language-name-changed", + G_CALLBACK (on_default_language_name_changed), + session); + g_signal_connect (session->priv->greeter, + "default-session-name-changed", + G_CALLBACK (on_default_session_name_changed), + session); + g_signal_connect (session->priv->greeter, + "timed-login-requested", + G_CALLBACK (on_timed_login_requested), + session); + g_signal_connect (session->priv->greeter, + "session-opened", + G_CALLBACK (on_session_opened), + session); +} + +static void +on_begin_verification (GdmGreeterLoginWindow *login_window, + const char *service_name, + GdmGreeterSession *session) +{ + get_user_verifier (session, NULL); + gdm_user_verifier_call_begin_verification_sync (session->priv->user_verifier, + service_name, + NULL, + NULL); +} + +static void +on_begin_verification_for_user (GdmGreeterLoginWindow *login_window, + const char *service_name, + const char *username, + GdmGreeterSession *session) +{ + get_user_verifier (session, NULL); + gdm_user_verifier_call_begin_verification_for_user_sync (session->priv->user_verifier, + service_name, + username, + NULL, + NULL); +} + +static void +on_query_answer (GdmGreeterLoginWindow *login_window, + const char *service_name, + const char *text, + GdmGreeterSession *session) +{ + gdm_user_verifier_call_answer_query_sync (session->priv->user_verifier, + service_name, + text, + NULL, + NULL); +} + +static void +on_select_session (GdmGreeterLoginWindow *login_window, + const char *text, + GdmGreeterSession *session) +{ + gdm_greeter_call_select_session_sync (session->priv->greeter, + text, + NULL, + NULL); +} + +static void +on_select_user (GdmGreeterLoginWindow *login_window, + const char *text, + GdmGreeterSession *session) +{ + show_or_hide_user_options (session, text); + gdm_greeter_call_select_user_sync (session->priv->greeter, + text, + NULL, + NULL); +} + +static void +on_cancelled (GdmGreeterLoginWindow *login_window, + GdmGreeterSession *session) +{ + gdm_user_verifier_call_cancel_sync (session->priv->user_verifier, NULL, NULL); +} + +static void +on_disconnected (GdmGreeterSession *session) +{ + if (session->priv->remote_greeter != NULL) { + gdm_remote_greeter_call_disconnect_sync (session->priv->remote_greeter, NULL, NULL); + } +} + +static void +on_start_session (GdmGreeterLoginWindow *login_window, + const char *service_name, + GdmGreeterSession *session) +{ + gdm_greeter_call_start_session_when_ready_sync (session->priv->greeter, service_name, TRUE, NULL, NULL); +} + +static int +get_tallest_monitor_at_point (GdkScreen *screen, + int x, + int y) +{ + cairo_rectangle_int_t area; + cairo_region_t *region; + int i; + int monitor; + int n_monitors; + int tallest_height; + + tallest_height = 0; + n_monitors = gdk_screen_get_n_monitors (screen); + monitor = -1; + for (i = 0; i < n_monitors; i++) { + gdk_screen_get_monitor_geometry (screen, i, &area); + region = cairo_region_create_rectangle (&area); + + if (cairo_region_contains_point (region, x, y)) { + if (area.height > tallest_height) { + monitor = i; + tallest_height = area.height; + } + } + cairo_region_destroy (region); + } + + if (monitor == -1) { + monitor = gdk_screen_get_monitor_at_point (screen, x, y); + } + + return monitor; +} + +static void +toggle_panel (GdmGreeterSession *session, + gboolean enabled) +{ + gdm_profile_start (NULL); + + if (enabled) { + GdkDisplay *display; + GdkScreen *screen; + int monitor; + int x, y; + gboolean is_local; + + display = gdk_display_get_default (); + gdk_display_get_pointer (display, &screen, &x, &y, NULL); + + monitor = get_tallest_monitor_at_point (screen, x, y); + + is_local = session->priv->remote_greeter != NULL; + session->priv->panel = gdm_greeter_panel_new (screen, monitor, is_local); + + g_signal_connect_swapped (session->priv->panel, + "disconnected", + G_CALLBACK (on_disconnected), + session); + + gtk_widget_show (session->priv->panel); + } else { + gtk_widget_destroy (session->priv->panel); + session->priv->panel = NULL; + } + + gdm_profile_end (NULL); +} + +static void +toggle_login_window (GdmGreeterSession *session, + gboolean enabled) +{ + gdm_profile_start (NULL); + + if (enabled) { + gboolean is_local; + + is_local = session->priv->remote_greeter != NULL; + g_debug ("GdmGreeterSession: Starting a login window local:%d", is_local); + session->priv->login_window = gdm_greeter_login_window_new (is_local); + g_signal_connect (session->priv->login_window, + "begin-auto-login", + G_CALLBACK (on_begin_auto_login), + session); + g_signal_connect (session->priv->login_window, + "begin-verification", + G_CALLBACK (on_begin_verification), + session); + g_signal_connect (session->priv->login_window, + "begin-verification-for-user", + G_CALLBACK (on_begin_verification_for_user), + session); + g_signal_connect (session->priv->login_window, + "query-answer", + G_CALLBACK (on_query_answer), + session); + g_signal_connect (session->priv->login_window, + "user-selected", + G_CALLBACK (on_select_user), + session); + g_signal_connect (session->priv->login_window, + "session-selected", + G_CALLBACK (on_select_session), + session); + g_signal_connect (session->priv->login_window, + "cancelled", + G_CALLBACK (on_cancelled), + session); + g_signal_connect (session->priv->login_window, + "start-session", + G_CALLBACK (on_start_session), + session); + gtk_widget_show (session->priv->login_window); + } else { + gtk_widget_destroy (session->priv->login_window); + session->priv->login_window = NULL; + } + gdm_profile_end (NULL); +} + +gboolean +gdm_greeter_session_start (GdmGreeterSession *session, + GError **error) +{ + g_return_val_if_fail (GDM_IS_GREETER_SESSION (session), FALSE); + + gdm_profile_start (NULL); + + + session->priv->greeter = gdm_client_get_greeter_sync (session->priv->client, + NULL, + error); + + if (session->priv->greeter == NULL) { + return FALSE; + } + + session->priv->remote_greeter = gdm_client_get_remote_greeter_sync (session->priv->client, + NULL, + error); + + + toggle_panel (session, TRUE); + toggle_login_window (session, TRUE); + + gdm_profile_end (NULL); + + return TRUE; +} + +void +gdm_greeter_session_stop (GdmGreeterSession *session) +{ + g_return_if_fail (GDM_IS_GREETER_SESSION (session)); + + toggle_panel (session, FALSE); + toggle_login_window (session, FALSE); +} + +static void +gdm_greeter_session_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_greeter_session_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gdm_greeter_session_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmGreeterSession *greeter_session; + + greeter_session = GDM_GREETER_SESSION (G_OBJECT_CLASS (gdm_greeter_session_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + return G_OBJECT (greeter_session); +} + +static void +gdm_greeter_session_dispose (GObject *object) +{ + g_debug ("GdmGreeterSession: Disposing greeter_session"); + + G_OBJECT_CLASS (gdm_greeter_session_parent_class)->dispose (object); +} + +static void +gdm_greeter_session_class_init (GdmGreeterSessionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gdm_greeter_session_get_property; + object_class->set_property = gdm_greeter_session_set_property; + object_class->constructor = gdm_greeter_session_constructor; + object_class->dispose = gdm_greeter_session_dispose; + object_class->finalize = gdm_greeter_session_finalize; + + g_type_class_add_private (klass, sizeof (GdmGreeterSessionPrivate)); +} + +static void +gdm_greeter_session_event_handler (GdkEvent *event, + GdmGreeterSession *session) +{ + g_assert (GDM_IS_GREETER_SESSION (session)); + + if (event->type == GDK_KEY_PRESS) { + GdkEventKey *key_event; + + key_event = (GdkEventKey *) event; + if (session->priv->panel != NULL) { + if (gtk_window_activate_key (GTK_WINDOW (session->priv->panel), + key_event)) { + gtk_window_present_with_time (GTK_WINDOW (session->priv->panel), + key_event->time); + return; + } + } + + if (session->priv->login_window != NULL) { + if (gtk_window_activate_key (GTK_WINDOW (session->priv->login_window), + ((GdkEventKey *) event))) { + gtk_window_present_with_time (GTK_WINDOW (session->priv->login_window), + key_event->time); + return; + } + } + } + + gtk_main_do_event (event); +} + +static void +gdm_greeter_session_init (GdmGreeterSession *session) +{ + gdm_profile_start (NULL); + + session->priv = GDM_GREETER_SESSION_GET_PRIVATE (session); + + session->priv->client = gdm_client_new (); + /* We want to listen for panel mnemonics even if the + * login window is focused, so we intercept them here. + */ + gdk_event_handler_set ((GdkEventFunc) gdm_greeter_session_event_handler, + session, NULL); + + gdm_profile_end (NULL); +} + +static void +gdm_greeter_session_finalize (GObject *object) +{ + GdmGreeterSession *greeter_session; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_GREETER_SESSION (object)); + + greeter_session = GDM_GREETER_SESSION (object); + + g_return_if_fail (greeter_session->priv != NULL); + + G_OBJECT_CLASS (gdm_greeter_session_parent_class)->finalize (object); +} + +GdmGreeterSession * +gdm_greeter_session_new (void) +{ + if (session_object != NULL) { + g_object_ref (session_object); + } else { + session_object = g_object_new (GDM_TYPE_GREETER_SESSION, NULL); + g_object_add_weak_pointer (session_object, + (gpointer *) &session_object); + } + + return GDM_GREETER_SESSION (session_object); +} diff --git a/gui/simple-greeter/gdm-greeter-session.h b/gui/simple-greeter/gdm-greeter-session.h new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef __GDM_GREETER_SESSION_H +#define __GDM_GREETER_SESSION_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_GREETER_SESSION (gdm_greeter_session_get_type ()) +#define GDM_GREETER_SESSION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_GREETER_SESSION, GdmGreeterSession)) +#define GDM_GREETER_SESSION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_GREETER_SESSION, GdmGreeterSessionClass)) +#define GDM_IS_GREETER_SESSION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_GREETER_SESSION)) +#define GDM_IS_GREETER_SESSION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_GREETER_SESSION)) +#define GDM_GREETER_SESSION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_GREETER_SESSION, GdmGreeterSessionClass)) + +typedef struct GdmGreeterSessionPrivate GdmGreeterSessionPrivate; + +typedef struct +{ + GObject parent; + GdmGreeterSessionPrivate *priv; +} GdmGreeterSession; + +typedef struct +{ + GObjectClass parent_class; +} GdmGreeterSessionClass; + +GType gdm_greeter_session_get_type (void); + +GdmGreeterSession * gdm_greeter_session_new (void); + +gboolean gdm_greeter_session_start (GdmGreeterSession *session, + GError **error); +void gdm_greeter_session_stop (GdmGreeterSession *session); + +G_END_DECLS + +#endif /* __GDM_GREETER_SESSION_H */ diff --git a/gui/simple-greeter/gdm-option-widget.c b/gui/simple-greeter/gdm-option-widget.c new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Written by: Ray Strode <rstrode@redhat.com> + * William Jon McCann <mccann@jhu.edu> + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <dirent.h> +#include <sys/stat.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gtk/gtk.h> + +#include "gdm-option-widget.h" + +#define GDM_OPTION_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_OPTION_WIDGET, GdmOptionWidgetPrivate)) + +#define GDM_OPTION_WIDGET_RC_STRING \ +"style \"gdm-option-widget-style\"" \ +"{" \ +" GtkComboBox::appears-as-list = 1" \ +"}" \ +"widget_class \"*<GdmOptionWidget>.*.GtkComboBox\" style \"gdm-option-widget-style\"" + +struct GdmOptionWidgetPrivate +{ + GtkWidget *label; + GtkWidget *image; + char *label_text; + char *icon_name; + char *default_item_id; + + GtkWidget *items_combo_box; + GtkListStore *list_store; + + GtkTreeModelFilter *model_filter; + GtkTreeModelSort *model_sorter; + + GtkTreeRowReference *active_row; + GtkTreeRowReference *top_separator_row; + GtkTreeRowReference *bottom_separator_row; + + gint number_of_top_rows; + gint number_of_middle_rows; + gint number_of_bottom_rows; + + guint check_idle_id; +}; + +enum { + PROP_0, + PROP_LABEL_TEXT, + PROP_ICON_NAME, + PROP_DEFAULT_ITEM +}; + +enum { + ACTIVATED = 0, + NUMBER_OF_SIGNALS +}; + +static guint signals[NUMBER_OF_SIGNALS]; + +static void gdm_option_widget_class_init (GdmOptionWidgetClass *klass); +static void gdm_option_widget_init (GdmOptionWidget *option_widget); +static void gdm_option_widget_finalize (GObject *object); + +G_DEFINE_TYPE (GdmOptionWidget, gdm_option_widget, GTK_TYPE_ALIGNMENT) +enum { + OPTION_NAME_COLUMN = 0, + OPTION_COMMENT_COLUMN, + OPTION_POSITION_COLUMN, + OPTION_ID_COLUMN, + NUMBER_OF_OPTION_COLUMNS +}; + +static gboolean +find_item (GdmOptionWidget *widget, + const char *id, + GtkTreeIter *iter) +{ + GtkTreeModel *model; + gboolean found_item; + + g_assert (GDM_IS_OPTION_WIDGET (widget)); + g_assert (id != NULL); + + found_item = FALSE; + model = GTK_TREE_MODEL (widget->priv->model_sorter); + + if (!gtk_tree_model_get_iter_first (model, iter)) { + return FALSE; + } + + do { + char *item_id; + + gtk_tree_model_get (model, iter, + OPTION_ID_COLUMN, &item_id, -1); + + g_assert (item_id != NULL); + + if (strcmp (id, item_id) == 0) { + found_item = TRUE; + } + g_free (item_id); + + } while (!found_item && gtk_tree_model_iter_next (model, iter)); + + return found_item; +} + +static char * +get_active_item_id (GdmOptionWidget *widget, + GtkTreeIter *iter) +{ + char *item_id; + GtkTreeModel *model; + GtkTreePath *path; + + g_return_val_if_fail (GDM_IS_OPTION_WIDGET (widget), NULL); + + model = GTK_TREE_MODEL (widget->priv->list_store); + item_id = NULL; + + if (widget->priv->active_row == NULL || + !gtk_tree_row_reference_valid (widget->priv->active_row)) { + return NULL; + } + + path = gtk_tree_row_reference_get_path (widget->priv->active_row); + if (gtk_tree_model_get_iter (model, iter, path)) { + gtk_tree_model_get (model, iter, + OPTION_ID_COLUMN, &item_id, -1); + }; + gtk_tree_path_free (path); + + return item_id; +} + +char * +gdm_option_widget_get_active_item (GdmOptionWidget *widget) +{ + GtkTreeIter iter; + + return get_active_item_id (widget, &iter); +} + +static void +activate_from_item_id (GdmOptionWidget *widget, + const char *item_id) +{ + GtkTreeIter iter; + + if (item_id == NULL) { + if (widget->priv->active_row != NULL) { + gtk_tree_row_reference_free (widget->priv->active_row); + widget->priv->active_row = NULL; + } + + gtk_combo_box_set_active (GTK_COMBO_BOX (widget->priv->items_combo_box), -1); + return; + } + + if (!find_item (widget, item_id, &iter)) { + g_critical ("Tried to activate non-existing item from option widget"); + return; + } + + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (widget->priv->items_combo_box), + &iter); +} + +static void +activate_from_row (GdmOptionWidget *widget, + GtkTreeRowReference *row) +{ + g_assert (row != NULL); + g_assert (gtk_tree_row_reference_valid (row)); + + if (widget->priv->active_row != NULL) { + gtk_tree_row_reference_free (widget->priv->active_row); + widget->priv->active_row = NULL; + } + + widget->priv->active_row = gtk_tree_row_reference_copy (row); + + g_signal_emit (widget, signals[ACTIVATED], 0); + +} + +static void +activate_selected_item (GdmOptionWidget *widget) +{ + GtkTreeModel *model; + GtkTreeIter sorted_iter; + gboolean is_already_active; + + model = GTK_TREE_MODEL (widget->priv->list_store); + is_already_active = FALSE; + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (widget->priv->items_combo_box), &sorted_iter)) { + GtkTreeRowReference *row; + GtkTreePath *sorted_path; + GtkTreePath *base_path; + + sorted_path = gtk_tree_model_get_path (GTK_TREE_MODEL (widget->priv->model_sorter), + &sorted_iter); + base_path = + gtk_tree_model_sort_convert_path_to_child_path (widget->priv->model_sorter, + sorted_path); + gtk_tree_path_free (sorted_path); + + if (widget->priv->active_row != NULL) { + GtkTreePath *active_path; + + active_path = gtk_tree_row_reference_get_path (widget->priv->active_row); + + if (active_path != NULL) { + if (gtk_tree_path_compare (base_path, active_path) == 0) { + is_already_active = TRUE; + } + gtk_tree_path_free (active_path); + } + } + g_assert (base_path != NULL); + row = gtk_tree_row_reference_new (model, base_path); + gtk_tree_path_free (base_path); + + if (!is_already_active) { + activate_from_row (widget, row); + } + + gtk_tree_row_reference_free (row); + } +} + +void +gdm_option_widget_set_active_item (GdmOptionWidget *widget, + const char *id) +{ + g_return_if_fail (GDM_IS_OPTION_WIDGET (widget)); + + activate_from_item_id (widget, id); +} + +char * +gdm_option_widget_get_default_item (GdmOptionWidget *widget) +{ + g_return_val_if_fail (GDM_IS_OPTION_WIDGET (widget), NULL); + + return g_strdup (widget->priv->default_item_id); +} + +void +gdm_option_widget_set_default_item (GdmOptionWidget *widget, + const char *item) +{ + char *active; + + g_return_if_fail (GDM_IS_OPTION_WIDGET (widget)); + g_return_if_fail (item == NULL || + gdm_option_widget_lookup_item (widget, item, + NULL, NULL, NULL)); + + if (widget->priv->default_item_id == NULL || + item == NULL || + strcmp (widget->priv->default_item_id, item) != 0) { + g_free (widget->priv->default_item_id); + widget->priv->default_item_id = NULL; + + if (widget->priv->active_row == NULL || item != NULL) { + activate_from_item_id (widget, item); + } + + widget->priv->default_item_id = g_strdup (item); + + g_object_notify (G_OBJECT (widget), "default-item"); + + } + + /* If a row has already been selected, then reset the selection to + * the active row. This way when a user fails to authenticate, any + * previously selected value will still be selected. + */ + active = gdm_option_widget_get_active_item (widget); + + if (active != NULL && item != NULL && + strcmp (gdm_option_widget_get_active_item (widget), + item) != 0) { + GtkTreeRowReference *row; + GtkTreePath *active_path; + GtkTreeModel *model; + + gdm_option_widget_set_active_item (widget, active); + active_path = gtk_tree_row_reference_get_path (widget->priv->active_row); + model = GTK_TREE_MODEL (widget->priv->list_store); + if (active_path != NULL) { + row = gtk_tree_row_reference_new (model, active_path); + activate_from_row (widget, row); + gtk_tree_path_free (active_path); + gtk_tree_row_reference_free (row); + } + } +} + +static const char * +gdm_option_widget_get_label_text (GdmOptionWidget *widget) +{ + return widget->priv->label_text; +} + +static void +gdm_option_widget_set_label_text (GdmOptionWidget *widget, + const char *text) +{ + if (widget->priv->label_text == NULL || + strcmp (widget->priv->label_text, text) != 0) { + g_free (widget->priv->label_text); + widget->priv->label_text = g_strdup (text); + gtk_widget_set_tooltip_markup (widget->priv->image, text); + g_object_notify (G_OBJECT (widget), "label-text"); + } +} + +static const char * +gdm_option_widget_get_icon_name (GdmOptionWidget *widget) +{ + return widget->priv->icon_name; +} + +static void +gdm_option_widget_set_icon_name (GdmOptionWidget *widget, + const char *name) +{ + if (name == NULL && widget->priv->icon_name != NULL) { + /* remove icon */ + g_free (widget->priv->icon_name); + widget->priv->icon_name = NULL; + gtk_widget_hide (widget->priv->image); + gtk_image_clear (GTK_IMAGE (widget->priv->image)); + g_object_notify (G_OBJECT (widget), "icon-name"); + } else if (name != NULL && widget->priv->icon_name == NULL) { + /* add icon */ + widget->priv->icon_name = g_strdup (name); + gtk_widget_show (widget->priv->image); + gtk_image_set_from_icon_name (GTK_IMAGE (widget->priv->image), name, GTK_ICON_SIZE_BUTTON); + g_object_notify (G_OBJECT (widget), "icon-name"); + } else if (name != NULL + && widget->priv->icon_name != NULL + && strcmp (widget->priv->icon_name, name) != 0) { + /* changed icon */ + g_free (widget->priv->icon_name); + widget->priv->icon_name = g_strdup (name); + gtk_image_set_from_icon_name (GTK_IMAGE (widget->priv->image), name, GTK_ICON_SIZE_BUTTON); + g_object_notify (G_OBJECT (widget), "icon-name"); + } +} + +static void +gdm_option_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmOptionWidget *self; + + self = GDM_OPTION_WIDGET (object); + + switch (prop_id) { + case PROP_LABEL_TEXT: + gdm_option_widget_set_label_text (self, g_value_get_string (value)); + break; + case PROP_ICON_NAME: + gdm_option_widget_set_icon_name (self, g_value_get_string (value)); + break; + case PROP_DEFAULT_ITEM: + gdm_option_widget_set_default_item (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_option_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmOptionWidget *self; + + self = GDM_OPTION_WIDGET (object); + + switch (prop_id) { + case PROP_LABEL_TEXT: + g_value_set_string (value, + gdm_option_widget_get_label_text (self)); + break; + case PROP_ICON_NAME: + g_value_set_string (value, + gdm_option_widget_get_icon_name (self)); + break; + case PROP_DEFAULT_ITEM: + g_value_take_string (value, + gdm_option_widget_get_default_item (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gdm_option_widget_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmOptionWidget *option_widget; + + option_widget = GDM_OPTION_WIDGET (G_OBJECT_CLASS (gdm_option_widget_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + return G_OBJECT (option_widget); +} + +static void +gdm_option_widget_dispose (GObject *object) +{ + GdmOptionWidget *widget; + + widget = GDM_OPTION_WIDGET (object); + + if (widget->priv->top_separator_row != NULL) { + gtk_tree_row_reference_free (widget->priv->top_separator_row); + widget->priv->top_separator_row = NULL; + } + + if (widget->priv->bottom_separator_row != NULL) { + gtk_tree_row_reference_free (widget->priv->bottom_separator_row); + widget->priv->bottom_separator_row = NULL; + } + + if (widget->priv->active_row != NULL) { + gtk_tree_row_reference_free (widget->priv->active_row); + widget->priv->active_row = NULL; + } + + G_OBJECT_CLASS (gdm_option_widget_parent_class)->dispose (object); +} + +static void +gdm_option_widget_class_init (GdmOptionWidgetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gdm_option_widget_get_property; + object_class->set_property = gdm_option_widget_set_property; + object_class->constructor = gdm_option_widget_constructor; + object_class->dispose = gdm_option_widget_dispose; + object_class->finalize = gdm_option_widget_finalize; + + gtk_rc_parse_string (GDM_OPTION_WIDGET_RC_STRING); + + signals [ACTIVATED] = g_signal_new ("activated", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmOptionWidgetClass, activated), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + g_object_class_install_property (object_class, + PROP_LABEL_TEXT, + g_param_spec_string ("label-text", + _("Label Text"), + _("The text to use as a label"), + NULL, + (G_PARAM_READWRITE | + G_PARAM_CONSTRUCT))); + g_object_class_install_property (object_class, + PROP_ICON_NAME, + g_param_spec_string ("icon-name", + _("Icon name"), + _("The icon to use with the label"), + NULL, + (G_PARAM_READWRITE | + G_PARAM_CONSTRUCT))); + + g_object_class_install_property (object_class, + PROP_DEFAULT_ITEM, + g_param_spec_string ("default-item", + _("Default Item"), + _("The ID of the default item"), + NULL, + G_PARAM_READWRITE)); + + g_type_class_add_private (klass, sizeof (GdmOptionWidgetPrivate)); +} + +static void +on_changed (GtkComboBox *combo_box, + GdmOptionWidget *widget) +{ + if (widget->priv->default_item_id == NULL) { + return; + } + + activate_selected_item (widget); +} + +static void +on_default_item_changed (GdmOptionWidget *widget) +{ + gtk_widget_set_sensitive (widget->priv->items_combo_box, + widget->priv->default_item_id != NULL); + gtk_tree_model_filter_refilter (widget->priv->model_filter); +} + +static gboolean +path_is_row (GdmOptionWidget *widget, + GtkTreeModel *model, + GtkTreePath *path, + GtkTreeRowReference *row) +{ + GtkTreePath *row_path; + GtkTreePath *translated_path; + gboolean is_row; + + row_path = gtk_tree_row_reference_get_path (row); + + if (row_path == NULL) { + return FALSE; + } + + if (model == GTK_TREE_MODEL (widget->priv->model_sorter)) { + GtkTreePath *filtered_path; + + filtered_path = gtk_tree_model_sort_convert_path_to_child_path (widget->priv->model_sorter, path); + + translated_path = gtk_tree_model_filter_convert_path_to_child_path (widget->priv->model_filter, filtered_path); + gtk_tree_path_free (filtered_path); + } else if (model == GTK_TREE_MODEL (widget->priv->model_filter)) { + translated_path = gtk_tree_model_filter_convert_path_to_child_path (widget->priv->model_filter, path); + } else { + g_assert (model == GTK_TREE_MODEL (widget->priv->list_store)); + translated_path = gtk_tree_path_copy (path); + } + + if (gtk_tree_path_compare (row_path, translated_path) == 0) { + is_row = TRUE; + } else { + is_row = FALSE; + } + gtk_tree_path_free (translated_path); + + return is_row; +} + +static gboolean +path_is_top_separator (GdmOptionWidget *widget, + GtkTreeModel *model, + GtkTreePath *path) +{ + if (widget->priv->top_separator_row != NULL) { + if (path_is_row (widget, model, path, + widget->priv->top_separator_row)) { + return TRUE; + } + } + + return FALSE; +} + +static gboolean +path_is_bottom_separator (GdmOptionWidget *widget, + GtkTreeModel *model, + GtkTreePath *path) +{ + if (widget->priv->bottom_separator_row != NULL) { + + if (path_is_row (widget, model, path, + widget->priv->bottom_separator_row)) { + return TRUE; + } + } + + return FALSE; +} + +static gboolean +path_is_separator (GdmOptionWidget *widget, + GtkTreeModel *model, + GtkTreePath *path) +{ + return path_is_top_separator (widget, model, path) || + path_is_bottom_separator (widget, model, path); +} + +static gboolean +gdm_option_widget_check_visibility (GdmOptionWidget *widget) +{ + if ((widget->priv->number_of_middle_rows != 0) && + (widget->priv->number_of_top_rows > 0 || + widget->priv->number_of_middle_rows > 1 || + widget->priv->number_of_bottom_rows > 0)) { + gtk_widget_show (widget->priv->items_combo_box); + + if (widget->priv->icon_name != NULL) { + gtk_widget_show (widget->priv->image); + } + } else { + gtk_widget_hide (widget->priv->items_combo_box); + gtk_widget_hide (widget->priv->image); + } + + widget->priv->check_idle_id = 0; + return FALSE; +} + +static void +gdm_option_widget_queue_visibility_check (GdmOptionWidget *widget) +{ + if (widget->priv->check_idle_id == 0) { + widget->priv->check_idle_id = g_idle_add ((GSourceFunc) gdm_option_widget_check_visibility, widget); + } +} + +static gboolean +check_item_visibilty (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + GdmOptionWidget *widget; + GtkTreePath *path; + gboolean is_top_separator; + gboolean is_bottom_separator; + gboolean is_visible; + + g_assert (GDM_IS_OPTION_WIDGET (data)); + + widget = GDM_OPTION_WIDGET (data); + + path = gtk_tree_model_get_path (model, iter); + is_top_separator = path_is_top_separator (widget, model, path); + is_bottom_separator = path_is_bottom_separator (widget, model, path); + gtk_tree_path_free (path); + + if (is_top_separator) { + is_visible = widget->priv->number_of_top_rows > 0 && + widget->priv->number_of_middle_rows > 0; + } else if (is_bottom_separator) { + is_visible = widget->priv->number_of_bottom_rows > 0 && + widget->priv->number_of_middle_rows > 0; + } else { + is_visible = TRUE; + } + + gdm_option_widget_queue_visibility_check (widget); + + return is_visible; +} + +static int +compare_item (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer data) +{ + GdmOptionWidget *widget; + GtkTreePath *path; + gboolean a_is_separator; + gboolean b_is_separator; + char *name_a; + char *name_b; + int position_a; + int position_b; + int result; + + g_assert (GDM_IS_OPTION_WIDGET (data)); + + widget = GDM_OPTION_WIDGET (data); + + gtk_tree_model_get (model, a, + OPTION_NAME_COLUMN, &name_a, + OPTION_POSITION_COLUMN, &position_a, + -1); + + gtk_tree_model_get (model, b, + OPTION_NAME_COLUMN, &name_b, + OPTION_POSITION_COLUMN, &position_b, + -1); + + if (position_a != position_b) { + result = position_a - position_b; + goto out; + } + + if (position_a == GDM_OPTION_WIDGET_POSITION_MIDDLE) { + a_is_separator = FALSE; + } else { + path = gtk_tree_model_get_path (model, a); + a_is_separator = path_is_separator (widget, model, path); + gtk_tree_path_free (path); + } + + if (position_b == GDM_OPTION_WIDGET_POSITION_MIDDLE) { + b_is_separator = FALSE; + } else { + path = gtk_tree_model_get_path (model, b); + b_is_separator = path_is_separator (widget, model, path); + gtk_tree_path_free (path); + } + + if (a_is_separator && b_is_separator) { + result = 0; + goto out; + } + + if (!a_is_separator && !b_is_separator) { + result = g_utf8_collate (name_a, name_b); + goto out; + } + + g_assert (position_a == position_b); + g_assert (position_a != GDM_OPTION_WIDGET_POSITION_MIDDLE); + + result = a_is_separator - b_is_separator; + + if (position_a == GDM_OPTION_WIDGET_POSITION_BOTTOM) { + result *= -1; + } +out: + g_free (name_a); + g_free (name_b); + + return result; +} + +static void +name_cell_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + GdmOptionWidget *widget) +{ + char *name; + char *id; + char *markup; + gboolean is_default; + + name = NULL; + gtk_tree_model_get (model, + iter, + OPTION_ID_COLUMN, &id, + OPTION_NAME_COLUMN, &name, + -1); + + if (widget->priv->default_item_id != NULL && + id != NULL && + strcmp (widget->priv->default_item_id, id) == 0) { + is_default = TRUE; + } else { + is_default = FALSE; + } + g_free (id); + id = NULL; + + markup = g_strdup_printf ("<span size='small'>%s%s%s</span>", + is_default? "<i>" : "", + name ? name : "", + is_default? "</i>" : ""); + g_free (name); + + g_object_set (cell, "markup", markup, NULL); + g_free (markup); +} + +static gboolean +separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + GdmOptionWidget *widget; + GtkTreePath *path; + gboolean is_separator; + + g_assert (GDM_IS_OPTION_WIDGET (data)); + + widget = GDM_OPTION_WIDGET (data); + + path = gtk_tree_model_get_path (model, iter); + + is_separator = path_is_separator (widget, model, path); + + gtk_tree_path_free (path); + + return is_separator; +} + +static void +add_separators (GdmOptionWidget *widget) +{ + GtkTreeIter iter; + GtkTreeModel *model; + GtkTreePath *path; + + g_assert (widget->priv->top_separator_row == NULL); + g_assert (widget->priv->bottom_separator_row == NULL); + + model = GTK_TREE_MODEL (widget->priv->list_store); + + gtk_list_store_insert_with_values (widget->priv->list_store, + &iter, 0, + OPTION_ID_COLUMN, "--", + OPTION_POSITION_COLUMN, GDM_OPTION_WIDGET_POSITION_BOTTOM, + -1); + path = gtk_tree_model_get_path (model, &iter); + widget->priv->bottom_separator_row = + gtk_tree_row_reference_new (model, path); + gtk_tree_path_free (path); + + gtk_list_store_insert_with_values (widget->priv->list_store, + &iter, 0, + OPTION_ID_COLUMN, "-", + OPTION_POSITION_COLUMN, GDM_OPTION_WIDGET_POSITION_TOP, + -1); + path = gtk_tree_model_get_path (model, &iter); + widget->priv->top_separator_row = + gtk_tree_row_reference_new (model, path); + gtk_tree_path_free (path); +} + +static gboolean +on_combo_box_mnemonic_activate (GtkWidget *widget, + gboolean arg1, + gpointer user_data) +{ + g_return_val_if_fail (GTK_IS_COMBO_BOX (widget), FALSE); + gtk_combo_box_popup (GTK_COMBO_BOX (widget)); + + return TRUE; +} + +static void +gdm_option_widget_init (GdmOptionWidget *widget) +{ + GtkWidget *box; + GtkCellRenderer *renderer; + + widget->priv = GDM_OPTION_WIDGET_GET_PRIVATE (widget); + + gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 0, 0, 0); + gtk_alignment_set (GTK_ALIGNMENT (widget), 0.5, 0.5, 0, 0); + + box = gtk_hbox_new (FALSE, 6); + gtk_widget_show (box); + gtk_container_add (GTK_CONTAINER (widget), + box); + + widget->priv->image = gtk_image_new (); + gtk_widget_set_no_show_all (widget->priv->image, TRUE); + gtk_box_pack_start (GTK_BOX (box), widget->priv->image, FALSE, FALSE, 0); + + widget->priv->items_combo_box = gtk_combo_box_new (); + + g_signal_connect (widget->priv->items_combo_box, + "changed", + G_CALLBACK (on_changed), + widget); + + /* We disable the combo box until it has a default + */ + gtk_widget_set_sensitive (widget->priv->items_combo_box, FALSE); + g_signal_connect (widget, + "notify::default-item", + G_CALLBACK (on_default_item_changed), + NULL); + + gtk_widget_set_no_show_all (widget->priv->items_combo_box, TRUE); + gtk_container_add (GTK_CONTAINER (box), + widget->priv->items_combo_box); + g_signal_connect (widget->priv->items_combo_box, + "mnemonic-activate", + G_CALLBACK (on_combo_box_mnemonic_activate), + NULL); + + g_assert (NUMBER_OF_OPTION_COLUMNS == 4); + widget->priv->list_store = gtk_list_store_new (NUMBER_OF_OPTION_COLUMNS, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_INT, + G_TYPE_STRING); + + + widget->priv->model_filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (widget->priv->list_store), NULL)); + + gtk_tree_model_filter_set_visible_func (widget->priv->model_filter, + check_item_visibilty, + widget, NULL); + + widget->priv->model_sorter = GTK_TREE_MODEL_SORT (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (widget->priv->model_filter))); + + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (widget->priv->model_sorter), + OPTION_ID_COLUMN, + compare_item, + widget, NULL); + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (widget->priv->model_sorter), + OPTION_ID_COLUMN, + GTK_SORT_ASCENDING); + gtk_combo_box_set_model (GTK_COMBO_BOX (widget->priv->items_combo_box), + GTK_TREE_MODEL (widget->priv->model_sorter)); + + add_separators (widget); + gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (widget->priv->items_combo_box), + separator_func, widget, NULL); + + /* NAME COLUMN */ + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget->priv->items_combo_box), renderer, FALSE); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (widget->priv->items_combo_box), + renderer, + (GtkCellLayoutDataFunc) name_cell_data_func, + widget, + NULL); +} + +static void +gdm_option_widget_finalize (GObject *object) +{ + GdmOptionWidget *widget; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_OPTION_WIDGET (object)); + + widget = GDM_OPTION_WIDGET (object); + + g_return_if_fail (widget->priv != NULL); + + g_free (widget->priv->icon_name); + g_free (widget->priv->label_text); + + G_OBJECT_CLASS (gdm_option_widget_parent_class)->finalize (object); +} + +GtkWidget * +gdm_option_widget_new (const char *label_text) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_OPTION_WIDGET, + "label-text", label_text, NULL); + + return GTK_WIDGET (object); +} + +void +gdm_option_widget_add_item (GdmOptionWidget *widget, + const char *id, + const char *name, + const char *comment, + GdmOptionWidgetPosition position) +{ + GtkTreeIter iter; + + g_return_if_fail (GDM_IS_OPTION_WIDGET (widget)); + + switch (position) { + case GDM_OPTION_WIDGET_POSITION_BOTTOM: + widget->priv->number_of_bottom_rows++; + break; + + case GDM_OPTION_WIDGET_POSITION_MIDDLE: + widget->priv->number_of_middle_rows++; + break; + + case GDM_OPTION_WIDGET_POSITION_TOP: + widget->priv->number_of_top_rows++; + break; + } + + gtk_list_store_insert_with_values (widget->priv->list_store, + &iter, 0, + OPTION_NAME_COLUMN, name, + OPTION_COMMENT_COLUMN, comment, + OPTION_POSITION_COLUMN, (int) position, + OPTION_ID_COLUMN, id, + -1); + gtk_tree_model_filter_refilter (widget->priv->model_filter); +} + +void +gdm_option_widget_remove_item (GdmOptionWidget *widget, + const char *id) +{ + GtkTreeModel *model; + GtkTreeIter iter; + int position; + + g_return_if_fail (GDM_IS_OPTION_WIDGET (widget)); + + model = GTK_TREE_MODEL (widget->priv->list_store); + + if (!find_item (widget, id, &iter)) { + g_critical ("Tried to remove non-existing item from option widget"); + return; + } + + if (widget->priv->default_item_id != NULL && + strcmp (widget->priv->default_item_id, id) == 0) { + g_critical ("Tried to remove default item from option widget"); + return; + } + + gtk_tree_model_get (model, &iter, + OPTION_POSITION_COLUMN, &position, + -1); + + switch ((GdmOptionWidgetPosition) position) { + case GDM_OPTION_WIDGET_POSITION_BOTTOM: + widget->priv->number_of_bottom_rows--; + break; + + case GDM_OPTION_WIDGET_POSITION_MIDDLE: + widget->priv->number_of_middle_rows--; + break; + + case GDM_OPTION_WIDGET_POSITION_TOP: + widget->priv->number_of_top_rows--; + break; + } + + gtk_list_store_remove (widget->priv->list_store, &iter); + gtk_tree_model_filter_refilter (widget->priv->model_filter); +} + +void +gdm_option_widget_remove_all_items (GdmOptionWidget *widget) +{ + GtkTreeIter iter; + GtkTreeModel *model; + int position; + gboolean is_valid; + + g_assert (GDM_IS_OPTION_WIDGET (widget)); + + model = GTK_TREE_MODEL (widget->priv->list_store); + + if (!gtk_tree_model_get_iter_first (model, &iter)) { + return; + } + + do { + gtk_tree_model_get (model, &iter, + OPTION_POSITION_COLUMN, &position, + -1); + + if ((GdmOptionWidgetPosition) position == GDM_OPTION_WIDGET_POSITION_MIDDLE) { + is_valid = gtk_list_store_remove (widget->priv->list_store, + &iter); + } else { + is_valid = gtk_tree_model_iter_next (model, &iter); + } + + + } while (is_valid); +} + +gboolean +gdm_option_widget_lookup_item (GdmOptionWidget *widget, + const char *id, + char **name, + char **comment, + GdmOptionWidgetPosition *position) +{ + GtkTreeIter iter; + char *active_item_id; + + g_return_val_if_fail (GDM_IS_OPTION_WIDGET (widget), FALSE); + g_return_val_if_fail (id != NULL, FALSE); + + active_item_id = get_active_item_id (widget, &iter); + + if (active_item_id == NULL || strcmp (active_item_id, id) != 0) { + g_free (active_item_id); + + if (!find_item (widget, id, &iter)) { + return FALSE; + } + } else { + g_free (active_item_id); + } + + if (name != NULL) { + gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, + OPTION_NAME_COLUMN, name, -1); + } + + if (comment != NULL) { + gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, + OPTION_COMMENT_COLUMN, comment, -1); + } + + if (position != NULL) { + int position_as_int; + + gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, + OPTION_POSITION_COLUMN, &position_as_int, -1); + + *position = (GdmOptionWidgetPosition) position_as_int; + } + + return TRUE; +} diff --git a/gui/simple-greeter/gdm-option-widget.h b/gui/simple-greeter/gdm-option-widget.h new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Written by: Ray Strode <rstrode@redhat.com> + * William Jon McCann <mccann@jhu.edu> + */ + +#ifndef __GDM_OPTION_WIDGET_H +#define __GDM_OPTION_WIDGET_H + +#include <glib-object.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_OPTION_WIDGET (gdm_option_widget_get_type ()) +#define GDM_OPTION_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_OPTION_WIDGET, GdmOptionWidget)) +#define GDM_OPTION_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_OPTION_WIDGET, GdmOptionWidgetClass)) +#define GDM_IS_OPTION_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_OPTION_WIDGET)) +#define GDM_IS_OPTION_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_OPTION_WIDGET)) +#define GDM_OPTION_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_OPTION_WIDGET, GdmOptionWidgetClass)) + +typedef struct GdmOptionWidgetPrivate GdmOptionWidgetPrivate; + +typedef struct +{ + GtkAlignment parent; + GdmOptionWidgetPrivate *priv; +} GdmOptionWidget; + +typedef struct +{ + GtkAlignmentClass parent_class; + + void (* activated) (GdmOptionWidget *widget); +} GdmOptionWidgetClass; + +typedef enum { + GDM_OPTION_WIDGET_POSITION_TOP = 0, + GDM_OPTION_WIDGET_POSITION_MIDDLE, + GDM_OPTION_WIDGET_POSITION_BOTTOM, +} GdmOptionWidgetPosition; + +GType gdm_option_widget_get_type (void); +GtkWidget * gdm_option_widget_new (const char *label_text); + +void gdm_option_widget_add_item (GdmOptionWidget *widget, + const char *id, + const char *name, + const char *comment, + GdmOptionWidgetPosition position); + +void gdm_option_widget_remove_item (GdmOptionWidget *widget, + const char *id); + +void gdm_option_widget_remove_all_items (GdmOptionWidget *widget); +gboolean gdm_option_widget_lookup_item (GdmOptionWidget *widget, + const char *id, + char **name, + char **comment, + GdmOptionWidgetPosition *position); + +char * gdm_option_widget_get_active_item (GdmOptionWidget *widget); +void gdm_option_widget_set_active_item (GdmOptionWidget *widget, + const char *item); +char * gdm_option_widget_get_default_item (GdmOptionWidget *widget); +void gdm_option_widget_set_default_item (GdmOptionWidget *widget, + const char *item); +G_END_DECLS + +#endif /* __GDM_OPTION_WIDGET_H */ diff --git a/gui/simple-greeter/gdm-remote-login-window.c b/gui/simple-greeter/gdm-remote-login-window.c new file mode 100644 index 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 <jmccann@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <errno.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-object.h> + +#include <gdk/gdkx.h> +#include <gtk/gtk.h> + +#include "gdm-remote-login-window.h" +#include "gdm-common.h" + +#define GDM_REMOTE_LOGIN_WINDOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_REMOTE_LOGIN_WINDOW, GdmRemoteLoginWindowPrivate)) + +struct GdmRemoteLoginWindowPrivate +{ + gboolean connected; + char *hostname; + char *display; + GPid xserver_pid; + guint xserver_watch_id; +}; + +enum { + PROP_0, +}; + +enum { + DISCONNECTED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gdm_remote_login_window_class_init (GdmRemoteLoginWindowClass *klass); +static void gdm_remote_login_window_init (GdmRemoteLoginWindow *remote_login_window); +static void gdm_remote_login_window_finalize (GObject *object); + +G_DEFINE_TYPE (GdmRemoteLoginWindow, gdm_remote_login_window, GTK_TYPE_WINDOW) + +static void +xserver_child_watch (GPid pid, + int status, + GdmRemoteLoginWindow *login_window) +{ + g_debug ("GdmRemoteLoginWindow: **** xserver (pid:%d) done (%s:%d)", + (int) pid, + WIFEXITED (status) ? "status" + : WIFSIGNALED (status) ? "signal" + : "unknown", + WIFEXITED (status) ? WEXITSTATUS (status) + : WIFSIGNALED (status) ? WTERMSIG (status) + : -1); + + g_spawn_close_pid (login_window->priv->xserver_pid); + + login_window->priv->xserver_pid = -1; + login_window->priv->xserver_watch_id = 0; + + gtk_widget_destroy (GTK_WIDGET (login_window)); +} + +static gboolean +start_xephyr (GdmRemoteLoginWindow *login_window) +{ + GError *local_error; + char **argv; + gboolean res; + gboolean ret; + int flags; + char *command; + + command = g_strdup_printf ("Xephyr -query %s -parent 0x%x -br -once %s", + login_window->priv->hostname, + (unsigned int)GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (login_window))), + login_window->priv->display); + g_debug ("GdmRemoteLoginWindow: Running: %s", command); + + ret = FALSE; + + argv = NULL; + local_error = NULL; + res = g_shell_parse_argv (command, NULL, &argv, &local_error); + if (! res) { + g_warning ("GdmRemoteLoginWindow: Unable to parse command: %s", local_error->message); + g_error_free (local_error); + goto out; + } + + flags = G_SPAWN_SEARCH_PATH + | G_SPAWN_DO_NOT_REAP_CHILD; + + local_error = NULL; + res = g_spawn_async (NULL, + argv, + NULL, + flags, + NULL, + NULL, + &login_window->priv->xserver_pid, + &local_error); + g_strfreev (argv); + + if (! res) { + g_warning ("GdmRemoteLoginWindow: Unable to run command %s: %s", + command, + local_error->message); + g_error_free (local_error); + goto out; + } + + g_debug ("GdmRemoteLoginWindow: Started: pid=%d command='%s'", + login_window->priv->xserver_pid, + command); + + login_window->priv->xserver_watch_id = g_child_watch_add (login_window->priv->xserver_pid, + (GChildWatchFunc)xserver_child_watch, + login_window); + ret = TRUE; + + out: + g_free (command); + + return ret; +} + +static gboolean +start_xdmx (GdmRemoteLoginWindow *login_window) +{ + char *cmd; + gboolean res; + GError *error; + + cmd = g_strdup_printf ("Xdmx -query %s -br -once %s", + login_window->priv->hostname, + login_window->priv->display); + g_debug ("Running: %s", cmd); + + error = NULL; + res = g_spawn_command_line_async (cmd, &error); + + g_free (cmd); + + if (! res) { + g_warning ("Could not start Xdmx X server: %s", error->message); + g_error_free (error); + return FALSE; + } + + return TRUE; +} + +gboolean +gdm_remote_login_window_connect (GdmRemoteLoginWindow *login_window, + const char *hostname) +{ + gboolean res; + char *title; + + title = g_strdup_printf (_("Remote Login (Connecting to %s…)"), hostname); + + gtk_window_set_title (GTK_WINDOW (login_window), title); + + login_window->priv->hostname = g_strdup (hostname); + login_window->priv->display = g_strdup (":300"); + + if (0) { + res = start_xdmx (login_window); + } else { + res = start_xephyr (login_window); + } + + if (res) { + title = g_strdup_printf (_("Remote Login (Connected to %s)"), hostname); + gtk_window_set_title (GTK_WINDOW (login_window), title); + g_free (title); + } + + return res; +} + +static void +gdm_remote_login_window_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_remote_login_window_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gdm_remote_login_window_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmRemoteLoginWindow *login_window; + + login_window = GDM_REMOTE_LOGIN_WINDOW (G_OBJECT_CLASS (gdm_remote_login_window_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + + return G_OBJECT (login_window); +} + +static void +gdm_remote_login_window_class_init (GdmRemoteLoginWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gdm_remote_login_window_get_property; + object_class->set_property = gdm_remote_login_window_set_property; + object_class->constructor = gdm_remote_login_window_constructor; + object_class->finalize = gdm_remote_login_window_finalize; + + signals [DISCONNECTED] = + g_signal_new ("disconnected", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmRemoteLoginWindowClass, disconnected), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + g_type_class_add_private (klass, sizeof (GdmRemoteLoginWindowPrivate)); +} + +static void +gdm_remote_login_window_init (GdmRemoteLoginWindow *login_window) +{ + login_window->priv = GDM_REMOTE_LOGIN_WINDOW_GET_PRIVATE (login_window); + + gtk_window_set_position (GTK_WINDOW (login_window), GTK_WIN_POS_CENTER_ALWAYS); + gtk_window_set_title (GTK_WINDOW (login_window), _("Remote Login")); + gtk_window_set_decorated (GTK_WINDOW (login_window), FALSE); + gtk_window_set_skip_taskbar_hint (GTK_WINDOW (login_window), TRUE); + gtk_window_set_skip_pager_hint (GTK_WINDOW (login_window), TRUE); + gtk_window_stick (GTK_WINDOW (login_window)); + gtk_window_maximize (GTK_WINDOW (login_window)); + gtk_window_set_icon_name (GTK_WINDOW (login_window), "computer"); +} + +static void +gdm_remote_login_window_finalize (GObject *object) +{ + GdmRemoteLoginWindow *login_window; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_REMOTE_LOGIN_WINDOW (object)); + + login_window = GDM_REMOTE_LOGIN_WINDOW (object); + + g_return_if_fail (login_window->priv != NULL); + + G_OBJECT_CLASS (gdm_remote_login_window_parent_class)->finalize (object); +} + +GtkWidget * +gdm_remote_login_window_new (gboolean is_local) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_REMOTE_LOGIN_WINDOW, + NULL); + + return GTK_WIDGET (object); +} diff --git a/gui/simple-greeter/gdm-remote-login-window.h b/gui/simple-greeter/gdm-remote-login-window.h new file mode 100644 index 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 <jmccann@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef __GDM_REMOTE_LOGIN_WINDOW_H +#define __GDM_REMOTE_LOGIN_WINDOW_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_REMOTE_LOGIN_WINDOW (gdm_remote_login_window_get_type ()) +#define GDM_REMOTE_LOGIN_WINDOW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_REMOTE_LOGIN_WINDOW, GdmRemoteLoginWindow)) +#define GDM_REMOTE_LOGIN_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_REMOTE_LOGIN_WINDOW, GdmRemoteLoginWindowClass)) +#define GDM_IS_REMOTE_LOGIN_WINDOW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_REMOTE_LOGIN_WINDOW)) +#define GDM_IS_REMOTE_LOGIN_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_REMOTE_LOGIN_WINDOW)) +#define GDM_REMOTE_LOGIN_WINDOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_REMOTE_LOGIN_WINDOW, GdmRemoteLoginWindowClass)) + +typedef struct GdmRemoteLoginWindowPrivate GdmRemoteLoginWindowPrivate; + +typedef struct +{ + GtkWindow parent; + GdmRemoteLoginWindowPrivate *priv; +} GdmRemoteLoginWindow; + +typedef struct +{ + GtkWindowClass parent_class; + + /* signals */ + void (* disconnected) (GdmRemoteLoginWindow *login_window); + +} GdmRemoteLoginWindowClass; + +GType gdm_remote_login_window_get_type (void); +GtkWidget * gdm_remote_login_window_new (gboolean display_is_local); + +gboolean gdm_remote_login_window_connect (GdmRemoteLoginWindow *login_window, + const char *host); +gboolean gdm_remote_login_window_discconnect (GdmRemoteLoginWindow *login_window); + +G_END_DECLS + +#endif /* __GDM_REMOTE_LOGIN_WINDOW_H */ diff --git a/gui/simple-greeter/gdm-scrollable-widget.c b/gui/simple-greeter/gdm-scrollable-widget.c new file mode 100644 index 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 <rstrode@redhat.com> + * + * Parts taken from gtkscrolledwindow.c in the GTK+ toolkit. + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> + +#include "gdm-scrollable-widget.h" +#include "gdm-timer.h" + +#define GDM_SCROLLABLE_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_SCROLLABLE_WIDGET, GdmScrollableWidgetPrivate)) + +enum +{ + SCROLL_CHILD, + MOVE_FOCUS_OUT, + NUMBER_OF_SIGNALS +}; + +typedef struct GdmScrollableWidgetAnimation GdmScrollableWidgetAnimation; + +struct GdmScrollableWidgetPrivate +{ + GtkWidget *scrollbar; + + GdmScrollableWidgetAnimation *animation; + GtkWidget *invisible_event_sink; + guint key_press_signal_id; + guint key_release_signal_id; + + int forced_height; + + GQueue *key_event_queue; +}; + +struct GdmScrollableWidgetAnimation +{ + GtkWidget *widget; + GdmTimer *timer; + int start_height; + int desired_height; + GdmScrollableWidgetSlideStepFunc step_func; + gpointer step_func_user_data; + GdmScrollableWidgetSlideDoneFunc done_func; + gpointer done_func_user_data; +}; + +static void gdm_scrollable_widget_class_init (GdmScrollableWidgetClass *klass); +static void gdm_scrollable_widget_init (GdmScrollableWidget *clock_widget); +static void gdm_scrollable_widget_finalize (GObject *object); + +static guint signals[NUMBER_OF_SIGNALS] = { 0 }; + +G_DEFINE_TYPE (GdmScrollableWidget, gdm_scrollable_widget, GTK_TYPE_BIN) + +static GdmScrollableWidgetAnimation * +gdm_scrollable_widget_animation_new (GtkWidget *widget, + int start_height, + int desired_height, + GdmScrollableWidgetSlideStepFunc step_func, + gpointer step_func_user_data, + GdmScrollableWidgetSlideDoneFunc done_func, + gpointer done_func_user_data) +{ + GdmScrollableWidgetAnimation *animation; + + animation = g_slice_new (GdmScrollableWidgetAnimation); + + animation->widget = widget; + animation->timer = gdm_timer_new (); + animation->start_height = start_height; + animation->desired_height = desired_height; + animation->step_func = step_func; + animation->step_func_user_data = step_func_user_data; + animation->done_func = done_func; + animation->done_func_user_data = done_func_user_data; + + return animation; +} + +static void +gdm_scrollable_widget_animation_free (GdmScrollableWidgetAnimation *animation) +{ + g_object_unref (animation->timer); + animation->timer = NULL; + g_slice_free (GdmScrollableWidgetAnimation, animation); +} + +static void +on_animation_tick (GdmScrollableWidgetAnimation *animation, + double progress) +{ + GdmScrollableWidget *scrollable_widget; + int progress_in_pixels; + int height; + + scrollable_widget = GDM_SCROLLABLE_WIDGET (animation->widget); + + progress_in_pixels = progress * (animation->start_height - animation->desired_height); + + height = animation->start_height - progress_in_pixels; + scrollable_widget->priv->forced_height = height; + + gtk_widget_queue_resize (animation->widget); + + if (animation->step_func != NULL) { + GdmTimer *timer; + GtkStyleContext *context; + GtkStateFlags state; + GtkBorder padding, border; + + height = animation->desired_height; + + context = gtk_widget_get_style_context (animation->widget); + state = gtk_widget_get_state_flags (animation->widget); + + gtk_style_context_get_padding (context, state, &padding); + gtk_style_context_get_border (context, state, &border); + + height -= padding.top + padding.bottom; + height -= border.top + border.bottom; + + timer = g_object_ref (animation->timer); + animation->step_func (GDM_SCROLLABLE_WIDGET (animation->widget), + progress, + &height, + animation->step_func_user_data); + + if (gdm_timer_is_started (timer)) { + height += padding.top + padding.bottom; + height += border.top + border.bottom; + + animation->desired_height = height; + } + g_object_unref (timer); + } +} + +static gboolean +on_key_event (GdmScrollableWidget *scrollable_widget, + GdkEventKey *key_event) +{ + g_queue_push_tail (scrollable_widget->priv->key_event_queue, + gdk_event_copy ((GdkEvent *)key_event)); + return FALSE; +} + +static gboolean +gdm_scrollable_redirect_input_to_event_sink (GdmScrollableWidget *scrollable_widget) +{ + GdkGrabStatus status; + + status = gdk_pointer_grab (gtk_widget_get_window (scrollable_widget->priv->invisible_event_sink), + FALSE, 0, NULL, NULL, GDK_CURRENT_TIME); + if (status != GDK_GRAB_SUCCESS) { + return FALSE; + } + + status = gdk_keyboard_grab (gtk_widget_get_window (scrollable_widget->priv->invisible_event_sink), + FALSE, GDK_CURRENT_TIME); + if (status != GDK_GRAB_SUCCESS) { + gdk_pointer_ungrab (GDK_CURRENT_TIME); + return FALSE; + } + + scrollable_widget->priv->key_press_signal_id = + g_signal_connect_swapped (scrollable_widget->priv->invisible_event_sink, + "key-press-event", G_CALLBACK (on_key_event), + scrollable_widget); + + scrollable_widget->priv->key_release_signal_id = + g_signal_connect_swapped (scrollable_widget->priv->invisible_event_sink, + "key-release-event", G_CALLBACK (on_key_event), + scrollable_widget); + + return TRUE; +} + +static void +gdm_scrollable_unredirect_input (GdmScrollableWidget *scrollable_widget) +{ + g_signal_handler_disconnect (scrollable_widget->priv->invisible_event_sink, + scrollable_widget->priv->key_press_signal_id); + scrollable_widget->priv->key_press_signal_id = 0; + + g_signal_handler_disconnect (scrollable_widget->priv->invisible_event_sink, + scrollable_widget->priv->key_release_signal_id); + scrollable_widget->priv->key_release_signal_id = 0; + gdk_keyboard_ungrab (GDK_CURRENT_TIME); + gdk_pointer_ungrab (GDK_CURRENT_TIME); +} + +static void +on_animation_stop (GdmScrollableWidgetAnimation *animation) +{ + GdmScrollableWidget *widget; + + widget = GDM_SCROLLABLE_WIDGET (animation->widget); + + if (animation->done_func != NULL) { + animation->done_func (widget, animation->done_func_user_data); + } + + gdm_scrollable_widget_animation_free (widget->priv->animation); + widget->priv->animation = NULL; + + gdm_scrollable_unredirect_input (widget); +} + +static void +gdm_scrollable_widget_animation_start (GdmScrollableWidgetAnimation *animation) +{ + g_signal_connect_swapped (G_OBJECT (animation->timer), "tick", + G_CALLBACK (on_animation_tick), + animation); + g_signal_connect_swapped (G_OBJECT (animation->timer), "stop", + G_CALLBACK (on_animation_stop), + animation); + gdm_timer_start (animation->timer, .10); +} + +static void +gdm_scrollable_widget_animation_stop (GdmScrollableWidgetAnimation *animation) +{ + gdm_timer_stop (animation->timer); +} + +static gboolean +gdm_scrollable_widget_needs_scrollbar (GdmScrollableWidget *widget) +{ + GtkWidget *child; + gboolean needs_scrollbar; + + if (widget->priv->scrollbar == NULL) { + return FALSE; + } + + if (widget->priv->animation != NULL) { + return FALSE; + } + + child = gtk_bin_get_child (GTK_BIN (widget)); + if (child != NULL && GTK_IS_SCROLLABLE (child)) { + GtkStyleContext *context; + GtkStateFlags state; + GtkBorder padding, border; + int available_height; + int child_scrolled_height; + + context = gtk_widget_get_style_context (GTK_WIDGET (widget)); + state = gtk_widget_get_state_flags (GTK_WIDGET (widget)); + + gtk_style_context_get_padding (context, state, &padding); + gtk_style_context_get_border (context, state, &border); + + available_height = gtk_widget_get_allocated_height (GTK_WIDGET (widget)); + available_height -= padding.top + padding.bottom; + available_height -= border.top + border.bottom; + + gtk_widget_get_preferred_height (child, NULL, &child_scrolled_height); + needs_scrollbar = child_scrolled_height > available_height; + } else { + needs_scrollbar = FALSE; + } + + return needs_scrollbar; +} + +static void +gdm_scrollable_widget_get_preferred_size (GtkWidget *widget, + GtkOrientation orientation, + gint *minimum_size, + gint *natural_size) +{ + GdmScrollableWidget *scrollable_widget; + GtkStyleContext *context; + GtkStateFlags state; + GtkBorder padding, border; + GtkRequisition scrollbar_requisition; + GtkRequisition minimum_req, natural_req; + GtkWidget *child; + int min_child_size, nat_child_size; + + context = gtk_widget_get_style_context (widget); + state = gtk_widget_get_state_flags (widget); + + gtk_style_context_get_padding (context, state, &padding); + gtk_style_context_get_border (context, state, &border); + + scrollable_widget = GDM_SCROLLABLE_WIDGET (widget); + + minimum_req.width = padding.left + padding.right; + minimum_req.width += border.left + border.right; + minimum_req.height = padding.top + padding.bottom; + minimum_req.height += border.top + border.bottom; + + natural_req.width = padding.left + padding.right; + natural_req.width += border.left + border.right; + natural_req.height = padding.top + padding.bottom; + natural_req.height += border.top + border.bottom; + + if (orientation == GTK_ORIENTATION_VERTICAL + && scrollable_widget->priv->forced_height >= 0) { + minimum_req.height += scrollable_widget->priv->forced_height; + natural_req.height += scrollable_widget->priv->forced_height; + } else { + child = gtk_bin_get_child (GTK_BIN (widget)); + + gtk_widget_get_preferred_size (scrollable_widget->priv->scrollbar, + &scrollbar_requisition, + NULL); + + if (child && gtk_widget_get_visible (child)) { + if (orientation == GTK_ORIENTATION_HORIZONTAL) { + gtk_widget_get_preferred_width (child, + &min_child_size, + &nat_child_size); + minimum_req.width += min_child_size; + natural_req.width += nat_child_size; + } else { + gtk_widget_get_preferred_height (child, + &min_child_size, + &nat_child_size); + + natural_req.height += nat_child_size; + } + } + + if (gdm_scrollable_widget_needs_scrollbar (scrollable_widget)) { + minimum_req.height = MAX (minimum_req.height, + scrollbar_requisition.height); + minimum_req.width += scrollbar_requisition.width; + natural_req.height = MAX (natural_req.height, + scrollbar_requisition.height); + natural_req.width += scrollbar_requisition.width; + } + } + + if (orientation == GTK_ORIENTATION_HORIZONTAL) { + if (minimum_size) + *minimum_size = minimum_req.width; + if (natural_size) + *natural_size = natural_req.width; + } else { + if (minimum_size) + *minimum_size = minimum_req.height; + if (natural_size) + *natural_size = natural_req.height; + } +} + +static void +gdm_scrollable_widget_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + gdm_scrollable_widget_get_preferred_size (widget, GTK_ORIENTATION_HORIZONTAL, minimum_size, natural_size); +} + +static void +gdm_scrollable_widget_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + gdm_scrollable_widget_get_preferred_size (widget, GTK_ORIENTATION_VERTICAL, minimum_size, natural_size); +} + +static void +gdm_scrollable_widget_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GdmScrollableWidget *scrollable_widget; + GtkAllocation scrollbar_allocation; + GtkAllocation child_allocation; + gboolean has_child; + gboolean needs_scrollbar; + gboolean is_flipped; + GtkWidget *child; + GtkStyleContext *context; + GtkStateFlags state; + GtkBorder padding, border; + + scrollable_widget = GDM_SCROLLABLE_WIDGET (widget); + context = gtk_widget_get_style_context (widget); + state = gtk_widget_get_state_flags (widget); + + gtk_style_context_get_padding (context, state, &padding); + gtk_style_context_get_border (context, state, &border); + + gtk_widget_set_allocation (widget, allocation); + + child = gtk_bin_get_child (GTK_BIN (widget)); + has_child = child && gtk_widget_get_visible (child); + needs_scrollbar = gdm_scrollable_widget_needs_scrollbar (scrollable_widget); + is_flipped = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL; + + if (needs_scrollbar) { + gtk_widget_show (scrollable_widget->priv->scrollbar); + + gtk_widget_get_preferred_width (scrollable_widget->priv->scrollbar, NULL, &scrollbar_allocation.width); + + if (!is_flipped) { + scrollbar_allocation.x = allocation->x + allocation->width; + scrollbar_allocation.x -= scrollbar_allocation.width - padding.right; + } else { + scrollbar_allocation.x = allocation->x + padding.right + border.right; + } + + scrollbar_allocation.height = allocation->height; + + scrollbar_allocation.y = allocation->y + padding.top; + + gtk_widget_size_allocate (scrollable_widget->priv->scrollbar, + &scrollbar_allocation); + } else { + gtk_widget_hide (scrollable_widget->priv->scrollbar); + } + + if (has_child) { + child_allocation.width = allocation->width; + child_allocation.width = MAX (child_allocation.width - padding.left, 1); + child_allocation.width = MAX (child_allocation.width - padding.right, 1); + child_allocation.width = MAX (child_allocation.width - border.left, 1); + child_allocation.width = MAX (child_allocation.width - border.right, 1); + + if (needs_scrollbar) { + child_allocation.width = MAX (child_allocation.width - scrollbar_allocation.width, 1); + } + + if (!is_flipped) { + child_allocation.x = allocation->x; + child_allocation.x += padding.left; + child_allocation.x += border.left; + } else { + child_allocation.x = allocation->x + allocation->width; + child_allocation.x -= child_allocation.width; + child_allocation.x -= padding.left; + child_allocation.x -= border.left; + } + + child_allocation.height = allocation->height; + child_allocation.height = MAX (child_allocation.height - padding.top, 1); + child_allocation.height = MAX (child_allocation.height - border.top, 1); + child_allocation.height = MAX (child_allocation.height - padding.bottom, 1); + child_allocation.height = MAX (child_allocation.height - border.bottom, 1); + + child_allocation.y = allocation->y; + child_allocation.y += padding.top; + child_allocation.y += border.top; + + gtk_widget_size_allocate (child, + &child_allocation); + } +} + +static void +gdm_scrollable_widget_add (GtkContainer *container, + GtkWidget *child) +{ + GtkAdjustment *adjustment; + + GTK_CONTAINER_CLASS (gdm_scrollable_widget_parent_class)->add (container, child); + + adjustment = gtk_range_get_adjustment (GTK_RANGE (GDM_SCROLLABLE_WIDGET (container)->priv->scrollbar)); + + g_signal_connect_swapped (adjustment, "changed", + G_CALLBACK (gtk_widget_queue_resize), + container); + + if (GTK_IS_SCROLLABLE (child)) + g_object_set (child, "hadjustment", NULL, "vadjustment", adjustment, NULL); + else + g_warning ("gdm_scrollable_widget_add(): cannot add non scrollable widget"); +} + +static void +gdm_scrollable_widget_remove (GtkContainer *container, + GtkWidget *child) +{ + g_object_set (child, "hadjustment", NULL, "vadjustment", NULL, NULL); + + GTK_CONTAINER_CLASS (gdm_scrollable_widget_parent_class)->remove (container, child); +} + +static void +gdm_scrollable_widget_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + + GdmScrollableWidget *scrollable_widget; + + scrollable_widget = GDM_SCROLLABLE_WIDGET (container); + + GTK_CONTAINER_CLASS (gdm_scrollable_widget_parent_class)->forall (container, + include_internals, + callback, + callback_data); + + if (!include_internals) { + return; + } + + if (scrollable_widget->priv->scrollbar != NULL) { + callback (scrollable_widget->priv->scrollbar, callback_data); + } +} + +static void +gdm_scrollable_widget_destroy (GtkWidget *object) +{ + GdmScrollableWidget *scrollable_widget; + + scrollable_widget = GDM_SCROLLABLE_WIDGET (object); + + gtk_widget_unparent (scrollable_widget->priv->scrollbar); + gtk_widget_destroy (scrollable_widget->priv->scrollbar); + + GTK_WIDGET_CLASS (gdm_scrollable_widget_parent_class)->destroy (object); +} + +static void +gdm_scrollable_widget_finalize (GObject *object) +{ + GdmScrollableWidget *scrollable_widget; + + scrollable_widget = GDM_SCROLLABLE_WIDGET (object); + + g_queue_free (scrollable_widget->priv->key_event_queue); + + G_OBJECT_CLASS (gdm_scrollable_widget_parent_class)->finalize (object); +} + +static gboolean +gdm_scrollable_widget_draw (GtkWidget *widget, + cairo_t *cr) +{ + GdmScrollableWidget *scrollable_widget; + int x; + int y; + int width; + int height; + gboolean is_flipped; + GtkStyleContext *context; + GtkStateFlags state; + GtkBorder padding, border; + GtkAllocation widget_allocation; + + context = gtk_widget_get_style_context (widget); + state = gtk_widget_get_state_flags (widget); + is_flipped = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL; + + gtk_style_context_get_padding (context, state, &padding); + gtk_style_context_get_border (context, state, &border); + + gtk_widget_get_allocation (widget, &widget_allocation); + + scrollable_widget = GDM_SCROLLABLE_WIDGET (widget); + + if (!gtk_widget_is_drawable (widget)) { + return FALSE; + } + + x = 0; + x += padding.left; + + width = widget_allocation.width; + width -= padding.left + padding.right; + + if (gdm_scrollable_widget_needs_scrollbar (scrollable_widget)) { + GtkAllocation scrollbar_allocation; + gtk_widget_get_allocation (scrollable_widget->priv->scrollbar, &scrollbar_allocation); + width -= scrollbar_allocation.width; + + if (is_flipped) { + x += scrollbar_allocation.width; + } + } + + y = 0; + y += padding.top; + + height = widget_allocation.height; + height -= padding.top + padding.bottom; + + if (width > 0 && height > 0) { + gtk_render_frame (context, cr, + x, y, width, height); + } + + return GTK_WIDGET_CLASS (gdm_scrollable_widget_parent_class)->draw (widget, cr); +} + +static gboolean +gdm_scrollable_widget_scroll_event (GtkWidget *widget, + GdkEventScroll *event) +{ + if (event->direction != GDK_SCROLL_UP && event->direction != GDK_SCROLL_DOWN) { + return FALSE; + } + + if (!gtk_widget_get_visible (GTK_WIDGET (widget))) { + return FALSE; + } + + return gtk_widget_event (GDM_SCROLLABLE_WIDGET (widget)->priv->scrollbar, + (GdkEvent *) event); +} + +static void +add_scroll_binding (GtkBindingSet *binding_set, + guint keyval, + GdkModifierType mask, + GtkScrollType scroll) +{ + guint keypad_keyval = keyval - GDK_KEY_Left + GDK_KEY_KP_Left; + + gtk_binding_entry_add_signal (binding_set, keyval, mask, + "scroll-child", 1, + GTK_TYPE_SCROLL_TYPE, scroll); + gtk_binding_entry_add_signal (binding_set, keypad_keyval, mask, + "scroll-child", 1, + GTK_TYPE_SCROLL_TYPE, scroll); +} + +static void +add_tab_bindings (GtkBindingSet *binding_set, + GdkModifierType modifiers, + GtkDirectionType direction) +{ + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Tab, modifiers, + "move-focus-out", 1, + GTK_TYPE_DIRECTION_TYPE, direction); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Tab, modifiers, + "move-focus-out", 1, + GTK_TYPE_DIRECTION_TYPE, direction); +} + +static void +gdm_scrollable_widget_class_install_bindings (GdmScrollableWidgetClass *klass) +{ + GtkBindingSet *binding_set; + + binding_set = gtk_binding_set_by_class (klass); + + add_scroll_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK, GTK_SCROLL_STEP_BACKWARD); + add_scroll_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK, GTK_SCROLL_STEP_FORWARD); + + add_scroll_binding (binding_set, GDK_KEY_Page_Up, 0, GTK_SCROLL_PAGE_BACKWARD); + add_scroll_binding (binding_set, GDK_KEY_Page_Down, 0, GTK_SCROLL_PAGE_FORWARD); + + add_scroll_binding (binding_set, GDK_KEY_Home, 0, GTK_SCROLL_START); + add_scroll_binding (binding_set, GDK_KEY_End, 0, GTK_SCROLL_END); + + add_tab_bindings (binding_set, GDK_CONTROL_MASK, GTK_DIR_TAB_FORWARD); + add_tab_bindings (binding_set, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_DIR_TAB_BACKWARD); +} + +static void +gdm_scrollable_widget_class_init (GdmScrollableWidgetClass *klass) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkContainerClass *container_class; + GdmScrollableWidgetClass *scrollable_widget_class; + + object_class = G_OBJECT_CLASS (klass); + widget_class = GTK_WIDGET_CLASS (klass); + container_class = GTK_CONTAINER_CLASS (klass); + scrollable_widget_class = GDM_SCROLLABLE_WIDGET_CLASS (klass); + + object_class->finalize = gdm_scrollable_widget_finalize; + + widget_class->destroy = gdm_scrollable_widget_destroy; + + widget_class->get_preferred_width = gdm_scrollable_widget_get_preferred_width; + widget_class->get_preferred_height = gdm_scrollable_widget_get_preferred_height; + widget_class->size_allocate = gdm_scrollable_widget_size_allocate; + widget_class->draw = gdm_scrollable_widget_draw; + widget_class->scroll_event = gdm_scrollable_widget_scroll_event; + + container_class->add = gdm_scrollable_widget_add; + container_class->remove = gdm_scrollable_widget_remove; + container_class->forall = gdm_scrollable_widget_forall; + gtk_container_class_handle_border_width (container_class); + + signals[SCROLL_CHILD] = + g_signal_new ("scroll-child", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkScrolledWindowClass, scroll_child), + NULL, NULL, + g_cclosure_marshal_VOID__ENUM, + G_TYPE_BOOLEAN, 1, + GTK_TYPE_SCROLL_TYPE); + signals[MOVE_FOCUS_OUT] = + g_signal_new ("move-focus-out", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkScrolledWindowClass, move_focus_out), + NULL, NULL, + g_cclosure_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + GTK_TYPE_DIRECTION_TYPE); + gdm_scrollable_widget_class_install_bindings (klass); + + g_type_class_add_private (klass, sizeof (GdmScrollableWidgetPrivate)); +} + +static void +gdm_scrollable_widget_add_scrollbar (GdmScrollableWidget *widget) +{ + gtk_widget_push_composite_child (); + widget->priv->scrollbar = gtk_vscrollbar_new (NULL); + g_object_set (widget->priv->scrollbar, "expand", TRUE, NULL); + gtk_widget_set_composite_name (widget->priv->scrollbar, "scrollbar"); + gtk_widget_pop_composite_child (); + gtk_widget_set_parent (widget->priv->scrollbar, GTK_WIDGET (widget)); + g_object_ref (widget->priv->scrollbar); +} + +static void +gdm_scrollable_widget_add_invisible_event_sink (GdmScrollableWidget *widget) +{ + widget->priv->invisible_event_sink = + gtk_invisible_new_for_screen (gtk_widget_get_screen (GTK_WIDGET (widget))); + gtk_widget_show (widget->priv->invisible_event_sink); + + widget->priv->key_event_queue = g_queue_new (); +} + +static void +gdm_scrollable_widget_adopt_style_from_scrolled_window_class (GdmScrollableWidget *widget) +{ + GtkStyleContext *context; + GtkWidgetPath *path; + + context = gtk_widget_get_style_context (GTK_WIDGET (widget)); + + gtk_style_context_add_class (context, GTK_STYLE_CLASS_FRAME); + + path = gtk_widget_path_new (); + gtk_widget_path_append_type (path, GTK_TYPE_SCROLLED_WINDOW); + gtk_style_context_set_path (context, path); + gtk_widget_path_free (path); +} + +static void +gdm_scrollable_widget_init (GdmScrollableWidget *widget) +{ + widget->priv = GDM_SCROLLABLE_WIDGET_GET_PRIVATE (widget); + + widget->priv->forced_height = -1; + + gdm_scrollable_widget_add_scrollbar (widget); + gdm_scrollable_widget_add_invisible_event_sink (widget); + + gdm_scrollable_widget_adopt_style_from_scrolled_window_class (widget); + g_signal_connect (G_OBJECT (widget), + "style-updated", + G_CALLBACK (gdm_scrollable_widget_adopt_style_from_scrolled_window_class), + NULL); +} + +GtkWidget * +gdm_scrollable_widget_new (void) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_SCROLLABLE_WIDGET, NULL); + + return GTK_WIDGET (object); +} + +static gboolean +gdm_scrollable_widget_animations_are_disabled (GdmScrollableWidget *scrollable_widget) +{ + GtkSettings *settings; + gboolean animations_are_enabled; + + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (scrollable_widget))); + g_object_get (settings, "gtk-enable-animations", &animations_are_enabled, NULL); + + return animations_are_enabled == FALSE; +} + +void +gdm_scrollable_widget_stop_sliding (GdmScrollableWidget *scrollable_widget) +{ + g_return_if_fail (GDM_IS_SCROLLABLE_WIDGET (scrollable_widget)); + + if (scrollable_widget->priv->animation != NULL) { + gdm_scrollable_widget_animation_stop (scrollable_widget->priv->animation); + } + + g_assert (scrollable_widget->priv->animation == NULL); +} + +void +gdm_scrollable_widget_slide_to_height (GdmScrollableWidget *scrollable_widget, + int height, + GdmScrollableWidgetSlideStepFunc step_func, + gpointer step_user_data, + GdmScrollableWidgetSlideDoneFunc done_func, + gpointer done_user_data) +{ + GtkWidget *widget; + gboolean input_redirected; + GtkAllocation widget_allocation; + GtkStyleContext *context; + GtkStateFlags state; + GtkBorder padding, border; + + g_return_if_fail (GDM_IS_SCROLLABLE_WIDGET (scrollable_widget)); + widget = GTK_WIDGET (scrollable_widget); + + gdm_scrollable_widget_stop_sliding (scrollable_widget); + + input_redirected = gdm_scrollable_redirect_input_to_event_sink (scrollable_widget); + + if (!input_redirected || gdm_scrollable_widget_animations_are_disabled (scrollable_widget)) { + scrollable_widget->priv->forced_height = height; + + if (step_func != NULL) { + step_func (scrollable_widget, 0.0, &height, step_user_data); + } + + if (done_func != NULL) { + done_func (scrollable_widget, done_user_data); + } + + if (input_redirected) { + gdm_scrollable_unredirect_input (scrollable_widget); + } + + return; + } + + context = gtk_widget_get_style_context (widget); + state = gtk_widget_get_state_flags (widget); + + gtk_style_context_get_padding (context, state, &padding); + gtk_style_context_get_border (context, state, &border); + + height += padding.top + padding.bottom; + height += border.top + border.bottom; + + gtk_widget_get_allocation (widget, &widget_allocation); + + scrollable_widget->priv->animation = + gdm_scrollable_widget_animation_new (widget, + widget_allocation.height, + height, step_func, step_user_data, + done_func, done_user_data); + + gdm_scrollable_widget_animation_start (scrollable_widget->priv->animation); +} + +gboolean +gdm_scrollable_widget_has_queued_key_events (GdmScrollableWidget *widget) +{ + g_return_val_if_fail (GDM_IS_SCROLLABLE_WIDGET (widget), FALSE); + + return !g_queue_is_empty (widget->priv->key_event_queue); +} + +void +gdm_scrollable_widget_replay_queued_key_events (GdmScrollableWidget *widget) +{ + GtkWidget *toplevel; + GdkEvent *event; + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (widget)); + + while ((event = g_queue_pop_head (widget->priv->key_event_queue)) != NULL) { + gtk_propagate_event (toplevel, event); + } +} diff --git a/gui/simple-greeter/gdm-scrollable-widget.h b/gui/simple-greeter/gdm-scrollable-widget.h new file mode 100644 index 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 <rstrode@redhat.com> + */ + +#ifndef __GDM_SCROLLABLE_WIDGET_H +#define __GDM_SCROLLABLE_WIDGET_H + +#include <glib-object.h> + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_SCROLLABLE_WIDGET (gdm_scrollable_widget_get_type ()) +#define GDM_SCROLLABLE_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_SCROLLABLE_WIDGET, GdmScrollableWidget)) +#define GDM_SCROLLABLE_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_SCROLLABLE_WIDGET, GdmScrollableWidgetClass)) +#define GDM_IS_SCROLLABLE_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_SCROLLABLE_WIDGET)) +#define GDM_IS_SCROLLABLE_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_SCROLLABLE_WIDGET)) +#define GDM_SCROLLABLE_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_SCROLLABLE_WIDGET, GdmScrollableWidgetClass)) + +typedef struct GdmScrollableWidgetClass GdmScrollableWidgetClass; +typedef struct GdmScrollableWidget GdmScrollableWidget; +typedef struct GdmScrollableWidgetPrivate GdmScrollableWidgetPrivate; +typedef void (* GdmScrollableWidgetSlideStepFunc) (GdmScrollableWidget *scrollable_widget, + double progress, + int *new_height, + gpointer *user_data); +typedef void (* GdmScrollableWidgetSlideDoneFunc) (GdmScrollableWidget *scrollable_widget, + gpointer *user_data); + +struct GdmScrollableWidget +{ + GtkBin parent; + GdmScrollableWidgetPrivate *priv; +}; + +struct GdmScrollableWidgetClass +{ + GtkBinClass parent_class; + + void (* scroll_child) (GdmScrollableWidget *widget, GtkScrollType type); + void (* move_focus_out) (GdmScrollableWidget *widget, GtkDirectionType type); + +}; + +GType gdm_scrollable_widget_get_type (void); +GtkWidget * gdm_scrollable_widget_new (void); +void gdm_scrollable_widget_stop_sliding (GdmScrollableWidget *widget); +void gdm_scrollable_widget_slide_to_height (GdmScrollableWidget *widget, + int height, + GdmScrollableWidgetSlideStepFunc step_func, + gpointer step_user_data, + GdmScrollableWidgetSlideDoneFunc done_func, + gpointer data); +gboolean gdm_scrollable_widget_has_queued_key_events (GdmScrollableWidget *widget); +void gdm_scrollable_widget_replay_queued_key_events (GdmScrollableWidget *widget); +#endif /* __GDM_SCROLLABLE_WIDGET_H */ diff --git a/gui/simple-greeter/gdm-session-option-widget.c b/gui/simple-greeter/gdm-session-option-widget.c new file mode 100644 index 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 <mccann@jhu.edu> + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Written by: William Jon McCann <mccann@jhu.edu> + * Ray Strode <rstrode@redhat.com> + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <dirent.h> +#include <sys/stat.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gtk/gtk.h> + +#include "gdm-session-option-widget.h" +#include "gdm-sessions.h" + +#define GDM_SESSION_OPTION_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_SESSION_OPTION_WIDGET, GdmSessionOptionWidgetPrivate)) + +#define GDM_SESSION_OPTION_WIDGET_LAST_SESSION "__previous" + +struct GdmSessionOptionWidgetPrivate +{ + gpointer dummy; +}; + +enum { + PROP_0, +}; + +enum { + SESSION_ACTIVATED, + NUMBER_OF_SIGNALS +}; + +static guint signals [NUMBER_OF_SIGNALS] = { 0, }; + +static void gdm_session_option_widget_class_init (GdmSessionOptionWidgetClass *klass); +static void gdm_session_option_widget_init (GdmSessionOptionWidget *session_option_widget); +static void gdm_session_option_widget_finalize (GObject *object); + +G_DEFINE_TYPE (GdmSessionOptionWidget, gdm_session_option_widget, GDM_TYPE_OPTION_WIDGET) +static void +gdm_session_option_widget_activated (GdmOptionWidget *widget) +{ + char *active_item_id; + + active_item_id = gdm_option_widget_get_active_item (GDM_OPTION_WIDGET (widget)); + if (active_item_id == NULL) { + return; + } + + g_signal_emit (G_OBJECT (widget), signals[SESSION_ACTIVATED], 0); +} + + +static void +gdm_session_option_widget_class_init (GdmSessionOptionWidgetClass *klass) +{ + GObjectClass *object_class; + GdmOptionWidgetClass *option_widget_class; + + object_class = G_OBJECT_CLASS (klass); + option_widget_class = GDM_OPTION_WIDGET_CLASS (klass); + + object_class->finalize = gdm_session_option_widget_finalize; + option_widget_class->activated = gdm_session_option_widget_activated; + + signals[SESSION_ACTIVATED] = g_signal_new ("session-activated", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmSessionOptionWidgetClass, session_activated), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + g_type_class_add_private (klass, sizeof (GdmSessionOptionWidgetPrivate)); +} + +static void +add_available_sessions (GdmSessionOptionWidget *widget) +{ + char **session_ids; + int i; + + session_ids = gdm_get_all_sessions (); + + for (i = 0; session_ids[i] != NULL; i++) { + char *name; + char *comment; + + if (!gdm_get_details_for_session (session_ids[i], + &name, &comment)) { + continue; + } + + gdm_option_widget_add_item (GDM_OPTION_WIDGET (widget), + session_ids[i], name, comment, + GDM_OPTION_WIDGET_POSITION_MIDDLE); + g_free (name); + g_free (comment); + } + + g_strfreev (session_ids); +} + +static void +gdm_session_option_widget_init (GdmSessionOptionWidget *widget) +{ + widget->priv = GDM_SESSION_OPTION_WIDGET_GET_PRIVATE (widget); + + add_available_sessions (widget); +} + +static void +gdm_session_option_widget_finalize (GObject *object) +{ + GdmSessionOptionWidget *session_option_widget; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_SESSION_OPTION_WIDGET (object)); + + session_option_widget = GDM_SESSION_OPTION_WIDGET (object); + + g_return_if_fail (session_option_widget->priv != NULL); + + G_OBJECT_CLASS (gdm_session_option_widget_parent_class)->finalize (object); +} + +GtkWidget * +gdm_session_option_widget_new (void) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_SESSION_OPTION_WIDGET, + "label-text", _("Session"), + NULL); + + return GTK_WIDGET (object); +} + +char * +gdm_session_option_widget_get_current_session (GdmSessionOptionWidget *widget) +{ + char *active_item_id; + + active_item_id = gdm_option_widget_get_active_item (GDM_OPTION_WIDGET (widget)); + if (active_item_id == NULL) { + return NULL; + } + + return active_item_id; +} + +void +gdm_session_option_widget_set_current_session (GdmSessionOptionWidget *widget, + const char *session) +{ + if (session == NULL) { + gdm_option_widget_set_active_item (GDM_OPTION_WIDGET (widget), + GDM_SESSION_OPTION_WIDGET_LAST_SESSION); + } else if (gdm_option_widget_lookup_item (GDM_OPTION_WIDGET (widget), session, + NULL, NULL, NULL)) { + gdm_option_widget_set_active_item (GDM_OPTION_WIDGET (widget), session); + } +} diff --git a/gui/simple-greeter/gdm-session-option-widget.h b/gui/simple-greeter/gdm-session-option-widget.h new file mode 100644 index 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 <rstrode@redhat.com> + */ + +#ifndef __GDM_SESSION_OPTION_WIDGET_H +#define __GDM_SESSION_OPTION_WIDGET_H + +#include <glib-object.h> + +#include "gdm-option-widget.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_SESSION_OPTION_WIDGET (gdm_session_option_widget_get_type ()) +#define GDM_SESSION_OPTION_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_SESSION_OPTION_WIDGET, GdmSessionOptionWidget)) +#define GDM_SESSION_OPTION_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_SESSION_OPTION_WIDGET, GdmSessionOptionWidgetClass)) +#define GDM_IS_SESSION_OPTION_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_SESSION_OPTION_WIDGET)) +#define GDM_IS_SESSION_OPTION_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_SESSION_OPTION_WIDGET)) +#define GDM_SESSION_OPTION_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_SESSION_OPTION_WIDGET, GdmSessionOptionWidgetClass)) + +typedef struct GdmSessionOptionWidgetPrivate GdmSessionOptionWidgetPrivate; + +typedef struct +{ + GdmOptionWidget parent; + GdmSessionOptionWidgetPrivate *priv; +} GdmSessionOptionWidget; + +typedef struct +{ + GdmOptionWidgetClass parent_class; + + void (* session_activated) (GdmSessionOptionWidget *widget); +} GdmSessionOptionWidgetClass; + +GType gdm_session_option_widget_get_type (void); +GtkWidget * gdm_session_option_widget_new (void); + +char * gdm_session_option_widget_get_current_session (GdmSessionOptionWidget *widget); +void gdm_session_option_widget_set_current_session (GdmSessionOptionWidget *widget, + const char *session); + + + +#endif /* __GDM_SESSION_OPTION_WIDGET_H */ diff --git a/gui/simple-greeter/gdm-sessions.c b/gui/simple-greeter/gdm-sessions.c new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Written by : William Jon McCann <mccann@jhu.edu> + * Ray Strode <rstrode@redhat.com> + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <dirent.h> +#include <sys/stat.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> + +#include "gdm-sessions.h" + +typedef struct _GdmSessionFile { + char *id; + char *path; + char *translated_name; + char *translated_comment; +} GdmSessionFile; + +static GHashTable *gdm_available_sessions_map; + +static gboolean gdm_sessions_map_is_initialized = FALSE; + +/* adapted from gnome-menus desktop-entries.c */ +static gboolean +key_file_is_relevant (GKeyFile *key_file) +{ + GError *error; + gboolean no_display; + gboolean hidden; + gboolean tryexec_failed; + char *tryexec; + + error = NULL; + no_display = g_key_file_get_boolean (key_file, + G_KEY_FILE_DESKTOP_GROUP, + "NoDisplay", + &error); + if (error) { + no_display = FALSE; + g_error_free (error); + } + + error = NULL; + hidden = g_key_file_get_boolean (key_file, + G_KEY_FILE_DESKTOP_GROUP, + "Hidden", + &error); + if (error) { + hidden = FALSE; + g_error_free (error); + } + + tryexec_failed = FALSE; + tryexec = g_key_file_get_string (key_file, + G_KEY_FILE_DESKTOP_GROUP, + "TryExec", + NULL); + if (tryexec) { + char *path; + + path = g_find_program_in_path (g_strstrip (tryexec)); + + tryexec_failed = (path == NULL); + + g_free (path); + g_free (tryexec); + } + + if (no_display || hidden || tryexec_failed) { + return FALSE; + } + + return TRUE; +} + +static void +load_session_file (const char *id, + const char *path) +{ + GKeyFile *key_file; + GError *error; + gboolean res; + GdmSessionFile *session; + + key_file = g_key_file_new (); + + error = NULL; + res = g_key_file_load_from_file (key_file, path, 0, &error); + + if (!res) { + g_debug ("Failed to load \"%s\": %s\n", path, error->message); + g_error_free (error); + goto out; + } + + if (! g_key_file_has_group (key_file, G_KEY_FILE_DESKTOP_GROUP)) { + goto out; + } + + res = g_key_file_has_key (key_file, G_KEY_FILE_DESKTOP_GROUP, "Name", NULL); + if (! res) { + g_debug ("\"%s\" contains no \"Name\" key\n", path); + goto out; + } + + if (!key_file_is_relevant (key_file)) { + g_debug ("\"%s\" is hidden or contains non-executable TryExec program\n", path); + goto out; + } + + session = g_new0 (GdmSessionFile, 1); + + session->id = g_strdup (id); + session->path = g_strdup (path); + + session->translated_name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, "Name", NULL, NULL); + session->translated_comment = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, "Comment", NULL, NULL); + + g_hash_table_insert (gdm_available_sessions_map, + g_strdup (id), + session); + out: + g_key_file_free (key_file); +} + +static void +collect_sessions_from_directory (const char *dirname) +{ + GDir *dir; + const char *filename; + + /* FIXME: add file monitor to directory */ + + dir = g_dir_open (dirname, 0, NULL); + if (dir == NULL) { + return; + } + + while ((filename = g_dir_read_name (dir))) { + char *id; + char *full_path; + + if (! g_str_has_suffix (filename, ".desktop")) { + continue; + } + id = g_strndup (filename, strlen (filename) - strlen (".desktop")); + + full_path = g_build_filename (dirname, filename, NULL); + + load_session_file (id, full_path); + + g_free (id); + g_free (full_path); + } + + g_dir_close (dir); +} + +static void +collect_sessions (void) +{ + int i; + const char *search_dirs[] = { + "/etc/X11/sessions/", + DMCONFDIR "/Sessions/", + DATADIR "/gdm/BuiltInSessions/", + DATADIR "/xsessions/", + NULL + }; + + if (gdm_available_sessions_map == NULL) { + gdm_available_sessions_map = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + } + + for (i = 0; search_dirs [i] != NULL; i++) { + collect_sessions_from_directory (search_dirs [i]); + } +} + +char ** +gdm_get_all_sessions (void) +{ + GHashTableIter iter; + gpointer key, value; + GPtrArray *array; + + if (!gdm_sessions_map_is_initialized) { + collect_sessions (); + + gdm_sessions_map_is_initialized = TRUE; + } + + array = g_ptr_array_new (); + g_hash_table_iter_init (&iter, gdm_available_sessions_map); + while (g_hash_table_iter_next (&iter, &key, &value)) { + GdmSessionFile *session; + + session = (GdmSessionFile *) value; + + g_ptr_array_add (array, g_strdup (session->id)); + } + g_ptr_array_add (array, NULL); + + return (char **) g_ptr_array_free (array, FALSE); +} + +gboolean +gdm_get_details_for_session (const char *id, + char **name, + char **comment) +{ + GdmSessionFile *session; + + if (!gdm_sessions_map_is_initialized) { + collect_sessions (); + + gdm_sessions_map_is_initialized = TRUE; + } + + session = (GdmSessionFile *) g_hash_table_lookup (gdm_available_sessions_map, + id); + + if (session == NULL) { + return FALSE; + } + + if (name != NULL) { + *name = g_strdup (session->translated_name); + } + + if (comment != NULL) { + *comment = g_strdup (session->translated_comment); + } + + return TRUE; +} diff --git a/gui/simple-greeter/gdm-sessions.h b/gui/simple-greeter/gdm-sessions.h new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Written by: Ray Strode + * William Jon McCann + */ + +#ifndef __GDM_SESSIONS_H +#define __GDM_SESSIONS_H + +#include <glib.h> + +G_BEGIN_DECLS + +char ** gdm_get_all_sessions (void); +gboolean gdm_get_details_for_session (const char *id, + char **name, + char **comment); + +G_END_DECLS + +#endif /* __GDM_SESSION_H */ diff --git a/gui/simple-greeter/gdm-timer.c b/gui/simple-greeter/gdm-timer.c new file mode 100644 index 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 <rstrode@redhat.com> + */ + +#include "config.h" +#include "gdm-timer.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <dirent.h> +#include <sys/stat.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gtk/gtk.h> + +#define GDM_TIMER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_TIMER, GdmTimerPrivate)) + +#ifndef GDM_TIMER_TICKS_PER_SECOND +#define GDM_TIMER_TICKS_PER_SECOND 60 +#endif + +struct GdmTimerPrivate +{ + double start_time; + double duration; + + guint tick_timeout_id; + + guint is_started : 1; +}; + +enum { + PROP_0, + PROP_START_TIME, + PROP_DURATION, + PROP_IS_STARTED +}; + +enum { + TICK, + STOP, + NUMBER_OF_SIGNALS +}; + +static guint signals[NUMBER_OF_SIGNALS]; + +static void gdm_timer_class_init (GdmTimerClass *klass); +static void gdm_timer_init (GdmTimer *timer); +static void gdm_timer_finalize (GObject *object); + +static void gdm_timer_queue_next_tick (GdmTimer *timer, + double when); + +G_DEFINE_TYPE (GdmTimer, gdm_timer, G_TYPE_OBJECT) + +static void +gdm_timer_finalize (GObject *object) +{ + GdmTimer *timer; + + timer = GDM_TIMER (object); + + gdm_timer_stop (timer); + + G_OBJECT_CLASS (gdm_timer_parent_class)->finalize (object); +} + +static void +gdm_timer_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmTimer *timer; + + timer = GDM_TIMER (object); + + switch (prop_id) { + case PROP_START_TIME: + g_value_set_double (value, timer->priv->start_time); + break; + case PROP_DURATION: + g_value_set_double (value, timer->priv->duration); + break; + case PROP_IS_STARTED: + g_value_set_boolean (value, timer->priv->is_started); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_timer_class_init (GdmTimerClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gdm_timer_finalize; + + object_class->get_property = gdm_timer_get_property; + + signals[TICK] = g_signal_new ("tick", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmTimerClass, tick), + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, G_TYPE_DOUBLE); + signals[STOP] = g_signal_new ("stop", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmTimerClass, stop), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_object_class_install_property (object_class, + PROP_DURATION, + g_param_spec_double ("duration", + _("Duration"), + _("Number of seconds until timer stops"), + 0.0, G_MAXDOUBLE, 0.0, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_START_TIME, + g_param_spec_double ("start-time", + _("Start time"), + _("Time the timer was started"), + 0.0, G_MAXDOUBLE, 0.0, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_IS_STARTED, + g_param_spec_boolean ("is-started", + _("Is it Running?"), + _("Whether the timer " + "is currently ticking"), + FALSE, G_PARAM_READABLE)); + + + + g_type_class_add_private (klass, sizeof (GdmTimerPrivate)); +} + +static void +gdm_timer_init (GdmTimer *timer) +{ + timer->priv = GDM_TIMER_GET_PRIVATE (timer); +} + +GdmTimer * +gdm_timer_new (void) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_TIMER, NULL); + + return GDM_TIMER (object); +} + +static double +get_current_time (void) +{ + const double microseconds_per_second = 1000000.0; + double timestamp; + GTimeVal now; + + g_get_current_time (&now); + + timestamp = ((microseconds_per_second * now.tv_sec) + now.tv_usec) / + microseconds_per_second; + + return timestamp; +} + +static double +do_tick (GdmTimer *timer, + double progress, + double current_time) +{ + static const double frequency = 1.0 / GDM_TIMER_TICKS_PER_SECOND; + double next_tick; + double time_before_tick; + double tick_duration; + + time_before_tick = current_time; + g_signal_emit (G_OBJECT (timer), signals[TICK], 0, progress); + + current_time = get_current_time (); + tick_duration = current_time - time_before_tick; + + next_tick = MAX (frequency - tick_duration, 0.0); + + return next_tick; +} + +static gboolean +on_tick_timeout (GdmTimer *timer) +{ + double progress; + double current_time; + double elapsed_time; + double next_tick; + + current_time = get_current_time (); + elapsed_time = current_time - timer->priv->start_time; + progress = elapsed_time / timer->priv->duration; + + timer->priv->tick_timeout_id = 0; + + g_object_ref (timer); + if (progress > 0.999) { + do_tick (timer, 1.0, current_time); + if (timer->priv->is_started) { + gdm_timer_stop (timer); + } + } else { + next_tick = do_tick (timer, progress, current_time); + if (timer->priv->is_started) { + gdm_timer_queue_next_tick (timer, next_tick); + } + } + g_object_unref (timer); + + return FALSE; +} + +static void +gdm_timer_queue_next_tick (GdmTimer *timer, + double when) +{ + if (timer->priv->tick_timeout_id != 0) { + return; + } + + if (!timer->priv->is_started) { + return; + } + + timer->priv->tick_timeout_id = g_timeout_add ((guint) (when * 1000), + (GSourceFunc) on_tick_timeout, + timer); +} + +static void +gdm_timer_set_is_started (GdmTimer *timer, + gboolean is_started) +{ + timer->priv->is_started = is_started; + g_object_notify (G_OBJECT (timer), "is-started"); +} + +void +gdm_timer_start (GdmTimer *timer, + double number_of_seconds) +{ + double next_tick; + + g_return_if_fail (GDM_IS_TIMER (timer)); + g_return_if_fail (number_of_seconds > G_MINDOUBLE); + g_return_if_fail (!timer->priv->is_started); + + timer->priv->start_time = get_current_time (); + timer->priv->duration = number_of_seconds; + + g_assert (timer->priv->tick_timeout_id == 0); + gdm_timer_set_is_started (timer, TRUE); + + g_object_ref (timer); + next_tick = do_tick (timer, 0.0, timer->priv->start_time); + gdm_timer_queue_next_tick (timer, next_tick); + g_object_unref (timer); +} + +void +gdm_timer_stop (GdmTimer *timer) +{ + g_return_if_fail (GDM_IS_TIMER (timer)); + + if (!timer->priv->is_started) { + return; + } + + if (timer->priv->tick_timeout_id != 0) { + g_source_remove (timer->priv->tick_timeout_id); + timer->priv->tick_timeout_id = 0; + } + + gdm_timer_set_is_started (timer, FALSE); + g_signal_emit (G_OBJECT (timer), signals[STOP], 0); +} + +gboolean +gdm_timer_is_started (GdmTimer *timer) +{ + g_return_val_if_fail (GDM_IS_TIMER (timer), FALSE); + + return timer->priv->is_started; +} diff --git a/gui/simple-greeter/gdm-timer.h b/gui/simple-greeter/gdm-timer.h new file mode 100644 index 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 <rstrode@redhat.com> + */ + +#ifndef GDM_TIMER_H +#define GDM_TIMER_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_TIMER (gdm_timer_get_type ()) +#define GDM_TIMER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_TIMER, GdmTimer)) +#define GDM_TIMER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_TIMER, GdmTimerClass)) +#define GDM_IS_TIMER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_TIMER)) +#define GDM_IS_TIMER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_TIMER)) +#define GDM_TIMER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_TIMER, GdmTimerClass)) + +typedef struct GdmTimerPrivate GdmTimerPrivate; + +typedef struct +{ + GObject parent; + GdmTimerPrivate *priv; +} GdmTimer; + +typedef struct +{ + GObjectClass parent_class; + + void (* tick) (GdmTimer *timer, double progress); + void (* stop) (GdmTimer *timer); +} GdmTimerClass; + +GType gdm_timer_get_type (void); +GdmTimer *gdm_timer_new (void); +#if 0 +GObject *gdm_timer_new_for_source (GdmTimerSource *source); +#endif +void gdm_timer_start (GdmTimer *timer, + double number_of_seconds); +void gdm_timer_stop (GdmTimer *timer); +gboolean gdm_timer_is_started (GdmTimer *timer); + +#endif /* GDM_TIMER_H */ diff --git a/gui/simple-greeter/gdm-user-chooser-dialog.c b/gui/simple-greeter/gdm-user-chooser-dialog.c new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <gtk/gtk.h> + +#include "gdm-user-chooser-widget.h" +#include "gdm-user-chooser-dialog.h" + +#define GDM_USER_CHOOSER_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_USER_CHOOSER_DIALOG, GdmUserChooserDialogPrivate)) + +struct GdmUserChooserDialogPrivate +{ + GtkWidget *chooser_widget; +}; + +enum { + PROP_0, +}; + +static void gdm_user_chooser_dialog_class_init (GdmUserChooserDialogClass *klass); +static void gdm_user_chooser_dialog_init (GdmUserChooserDialog *user_chooser_dialog); +static void gdm_user_chooser_dialog_finalize (GObject *object); + +G_DEFINE_TYPE (GdmUserChooserDialog, gdm_user_chooser_dialog, GTK_TYPE_DIALOG) + +char * +gdm_user_chooser_dialog_get_chosen_user_name (GdmUserChooserDialog *dialog) +{ + char *user_name; + + g_return_val_if_fail (GDM_IS_USER_CHOOSER_DIALOG (dialog), NULL); + + user_name = gdm_user_chooser_widget_get_chosen_user_name (GDM_USER_CHOOSER_WIDGET (dialog->priv->chooser_widget)); + + return user_name; +} + +void +gdm_user_chooser_dialog_set_show_user_guest (GdmUserChooserDialog *dialog, + gboolean show_user) +{ + g_return_if_fail (GDM_IS_USER_CHOOSER_DIALOG (dialog)); + + gdm_user_chooser_widget_set_show_user_guest (GDM_USER_CHOOSER_WIDGET (dialog->priv->chooser_widget), show_user); +} + +void +gdm_user_chooser_dialog_set_show_user_auto (GdmUserChooserDialog *dialog, + gboolean show_user) +{ + g_return_if_fail (GDM_IS_USER_CHOOSER_DIALOG (dialog)); + + gdm_user_chooser_widget_set_show_user_auto (GDM_USER_CHOOSER_WIDGET (dialog->priv->chooser_widget), show_user); +} + +static void +gdm_user_chooser_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_user_chooser_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gdm_user_chooser_dialog_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmUserChooserDialog *user_chooser_dialog; + + user_chooser_dialog = GDM_USER_CHOOSER_DIALOG (G_OBJECT_CLASS (gdm_user_chooser_dialog_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + return G_OBJECT (user_chooser_dialog); +} + +static void +gdm_user_chooser_dialog_dispose (GObject *object) +{ + G_OBJECT_CLASS (gdm_user_chooser_dialog_parent_class)->dispose (object); +} + +static void +gdm_user_chooser_dialog_class_init (GdmUserChooserDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gdm_user_chooser_dialog_get_property; + object_class->set_property = gdm_user_chooser_dialog_set_property; + object_class->constructor = gdm_user_chooser_dialog_constructor; + object_class->dispose = gdm_user_chooser_dialog_dispose; + object_class->finalize = gdm_user_chooser_dialog_finalize; + + g_type_class_add_private (klass, sizeof (GdmUserChooserDialogPrivate)); +} + +static void +on_response (GdmUserChooserDialog *dialog, + gint response_id) +{ + switch (response_id) { + default: + break; + } +} + +static void +gdm_user_chooser_dialog_init (GdmUserChooserDialog *dialog) +{ + + dialog->priv = GDM_USER_CHOOSER_DIALOG_GET_PRIVATE (dialog); + + dialog->priv->chooser_widget = gdm_user_chooser_widget_new (); + + gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), dialog->priv->chooser_widget); + + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_OK, + NULL); + g_signal_connect (dialog, + "response", + G_CALLBACK (on_response), + dialog); + + gtk_widget_show_all (GTK_WIDGET (dialog)); +} + +static void +gdm_user_chooser_dialog_finalize (GObject *object) +{ + GdmUserChooserDialog *user_chooser_dialog; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_USER_CHOOSER_DIALOG (object)); + + user_chooser_dialog = GDM_USER_CHOOSER_DIALOG (object); + + g_return_if_fail (user_chooser_dialog->priv != NULL); + + G_OBJECT_CLASS (gdm_user_chooser_dialog_parent_class)->finalize (object); +} + +GtkWidget * +gdm_user_chooser_dialog_new (void) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_USER_CHOOSER_DIALOG, + NULL); + + return GTK_WIDGET (object); +} diff --git a/gui/simple-greeter/gdm-user-chooser-dialog.h b/gui/simple-greeter/gdm-user-chooser-dialog.h new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef __GDM_USER_CHOOSER_DIALOG_H +#define __GDM_USER_CHOOSER_DIALOG_H + +#include <glib-object.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_USER_CHOOSER_DIALOG (gdm_user_chooser_dialog_get_type ()) +#define GDM_USER_CHOOSER_DIALOG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_USER_CHOOSER_DIALOG, GdmUserChooserDialog)) +#define GDM_USER_CHOOSER_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_USER_CHOOSER_DIALOG, GdmUserChooserDialogClass)) +#define GDM_IS_USER_CHOOSER_DIALOG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_USER_CHOOSER_DIALOG)) +#define GDM_IS_USER_CHOOSER_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_USER_CHOOSER_DIALOG)) +#define GDM_USER_CHOOSER_DIALOG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_USER_CHOOSER_DIALOG, GdmUserChooserDialogClass)) + +typedef struct GdmUserChooserDialogPrivate GdmUserChooserDialogPrivate; + +typedef struct +{ + GtkDialog parent; + GdmUserChooserDialogPrivate *priv; +} GdmUserChooserDialog; + +typedef struct +{ + GtkDialogClass parent_class; +} GdmUserChooserDialogClass; + +GType gdm_user_chooser_dialog_get_type (void); + +GtkWidget * gdm_user_chooser_dialog_new (void); + +char * gdm_user_chooser_dialog_get_chosen_user_name (GdmUserChooserDialog *dialog); +void gdm_user_chooser_dialog_set_show_other_user (GdmUserChooserDialog *dialog, + gboolean show); +void gdm_user_chooser_dialog_set_show_user_guest (GdmUserChooserDialog *dialog, + gboolean show); +void gdm_user_chooser_dialog_set_show_user_auto (GdmUserChooserDialog *dialog, + gboolean show); +G_END_DECLS + +#endif /* __GDM_USER_CHOOSER_DIALOG_H */ diff --git a/gui/simple-greeter/gdm-user-chooser-widget.c b/gui/simple-greeter/gdm-user-chooser-widget.c new file mode 100644 index 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 <mccann@jhu.edu> + * Copyright (C) 2007 Ray Strode <rstrode@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <dirent.h> +#include <stdarg.h> +#include <sys/stat.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gio/gio.h> +#include <gtk/gtk.h> + +#include <act/act-user-manager.h> +#include <act/act-user.h> + +#include "gdm-user-chooser-widget.h" +#include "gdm-settings-keys.h" +#include "gdm-settings-client.h" + +#define LOGIN_SCREEN_SCHEMA "org.gnome.login-screen" + +#define KEY_DISABLE_USER_LIST "disable-user-list" + +enum { + USER_NO_DISPLAY = 1 << 0, + USER_ACCOUNT_DISABLED = 1 << 1, +}; + +#define DEFAULT_USER_ICON "avatar-default" +#define OLD_DEFAULT_USER_ICON "stock_person" + +#define GDM_USER_CHOOSER_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_USER_CHOOSER_WIDGET, GdmUserChooserWidgetPrivate)) + +#define MAX_ICON_SIZE 128 +#define NUM_USERS_TO_ADD_PER_ITERATION 50 + +struct GdmUserChooserWidgetPrivate +{ + ActUserManager *manager; + GtkIconTheme *icon_theme; + + GSList *users_to_add; + + GdkPixbuf *logged_in_pixbuf; + GdkPixbuf *stock_person_pixbuf; + + guint loaded : 1; + guint show_user_other : 1; + guint show_user_guest : 1; + guint show_user_auto : 1; + guint show_normal_users : 1; + + guint has_user_other : 1; + + guint update_other_visibility_idle_id; + guint load_idle_id; + guint add_users_idle_id; +}; + +enum { + PROP_0, + PROP_SHOW_USER_GUEST, + PROP_SHOW_USER_AUTO, + PROP_SHOW_USER_OTHER, +}; + +static void gdm_user_chooser_widget_class_init (GdmUserChooserWidgetClass *klass); +static void gdm_user_chooser_widget_init (GdmUserChooserWidget *user_chooser_widget); +static void gdm_user_chooser_widget_finalize (GObject *object); + +G_DEFINE_TYPE (GdmUserChooserWidget, gdm_user_chooser_widget, GDM_TYPE_CHOOSER_WIDGET) + +static void add_user_other (GdmUserChooserWidget *widget); +static void remove_user_other (GdmUserChooserWidget *widget); + +static int +get_font_height_for_widget (GtkWidget *widget) +{ + PangoFontMetrics *metrics; + PangoContext *context; + int ascent; + int descent; + int height; + + gtk_widget_ensure_style (widget); + context = gtk_widget_get_pango_context (widget); + metrics = pango_context_get_metrics (context, + gtk_widget_get_style (widget)->font_desc, + pango_context_get_language (context)); + + ascent = pango_font_metrics_get_ascent (metrics); + descent = pango_font_metrics_get_descent (metrics); + height = PANGO_PIXELS (ascent + descent); + pango_font_metrics_unref (metrics); + return height; +} + +static int +get_icon_height_for_widget (GtkWidget *widget) +{ + int font_height; + int height; + + font_height = get_font_height_for_widget (widget); + height = 3 * font_height; + if (height > MAX_ICON_SIZE) { + height = MAX_ICON_SIZE; + } + + g_debug ("GdmUserChooserWidget: font height %d; using icon size %d", font_height, height); + + return height; +} + +static gboolean +update_other_user_visibility (GdmUserChooserWidget *widget) +{ + g_debug ("GdmUserChooserWidget: updating other user visibility"); + + if (!widget->priv->show_user_other) { + if (widget->priv->has_user_other) { + remove_user_other (widget); + } + + goto out; + } + + /* Always show the Other user if requested */ + if (!widget->priv->has_user_other) { + add_user_other (widget); + } + + out: + widget->priv->update_other_visibility_idle_id = 0; + return FALSE; +} + +static void +queue_update_other_user_visibility (GdmUserChooserWidget *widget) +{ + if (widget->priv->update_other_visibility_idle_id == 0) { + widget->priv->update_other_visibility_idle_id = + g_idle_add ((GSourceFunc) update_other_user_visibility, widget); + } +} + +static void +rounded_rectangle (cairo_t *cr, + gdouble aspect, + gdouble x, + gdouble y, + gdouble corner_radius, + gdouble width, + gdouble height) +{ + gdouble radius; + gdouble degrees; + + radius = corner_radius / aspect; + degrees = G_PI / 180.0; + + cairo_new_sub_path (cr); + cairo_arc (cr, + x + width - radius, + y + radius, + radius, + -90 * degrees, + 0 * degrees); + cairo_arc (cr, + x + width - radius, + y + height - radius, + radius, + 0 * degrees, + 90 * degrees); + cairo_arc (cr, + x + radius, + y + height - radius, + radius, + 90 * degrees, + 180 * degrees); + cairo_arc (cr, + x + radius, + y + radius, + radius, + 180 * degrees, + 270 * degrees); + cairo_close_path (cr); +} + +static cairo_surface_t * +surface_from_pixbuf (GdkPixbuf *pixbuf) +{ + cairo_surface_t *surface; + cairo_t *cr; + + surface = cairo_image_surface_create (gdk_pixbuf_get_has_alpha (pixbuf) ? + CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, + gdk_pixbuf_get_width (pixbuf), + gdk_pixbuf_get_height (pixbuf)); + cr = cairo_create (surface); + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + cairo_paint (cr); + cairo_destroy (cr); + + return surface; +} + +/** + * go_cairo_convert_data_to_pixbuf: + * @src: a pointer to pixel data in cairo format + * @dst: a pointer to pixel data in pixbuf format + * @width: image width + * @height: image height + * @rowstride: data rowstride + * + * Converts the pixel data stored in @src in CAIRO_FORMAT_ARGB32 cairo format + * to GDK_COLORSPACE_RGB pixbuf format and move them + * to @dst. If @src == @dst, pixel are converted in place. + **/ + +static void +go_cairo_convert_data_to_pixbuf (unsigned char *dst, + unsigned char const *src, + int width, + int height, + int rowstride) +{ + int i,j; + unsigned int t; + unsigned char a, b, c; + + g_return_if_fail (dst != NULL); + +#define MULT(d,c,a,t) G_STMT_START { t = (a)? c * 255 / a: 0; d = t;} G_STMT_END + + if (src == dst || src == NULL) { + for (i = 0; i < height; i++) { + for (j = 0; j < width; j++) { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + MULT(a, dst[2], dst[3], t); + MULT(b, dst[1], dst[3], t); + MULT(c, dst[0], dst[3], t); + dst[0] = a; + dst[1] = b; + dst[2] = c; +#else + MULT(a, dst[1], dst[0], t); + MULT(b, dst[2], dst[0], t); + MULT(c, dst[3], dst[0], t); + dst[3] = dst[0]; + dst[0] = a; + dst[1] = b; + dst[2] = c; +#endif + dst += 4; + } + dst += rowstride - width * 4; + } + } else { + for (i = 0; i < height; i++) { + for (j = 0; j < width; j++) { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + MULT(dst[0], src[2], src[3], t); + MULT(dst[1], src[1], src[3], t); + MULT(dst[2], src[0], src[3], t); + dst[3] = src[3]; +#else + MULT(dst[0], src[1], src[0], t); + MULT(dst[1], src[2], src[0], t); + MULT(dst[2], src[3], src[0], t); + dst[3] = src[0]; +#endif + src += 4; + dst += 4; + } + src += rowstride - width * 4; + dst += rowstride - width * 4; + } + } +#undef MULT +} + +static void +cairo_to_pixbuf (guint8 *src_data, + GdkPixbuf *dst_pixbuf) +{ + unsigned char *src; + unsigned char *dst; + guint w; + guint h; + guint rowstride; + + w = gdk_pixbuf_get_width (dst_pixbuf); + h = gdk_pixbuf_get_height (dst_pixbuf); + rowstride = gdk_pixbuf_get_rowstride (dst_pixbuf); + + dst = gdk_pixbuf_get_pixels (dst_pixbuf); + src = src_data; + + go_cairo_convert_data_to_pixbuf (dst, src, w, h, rowstride); +} + +static GdkPixbuf * +frame_pixbuf (GdkPixbuf *source) +{ + GdkPixbuf *dest; + cairo_t *cr; + cairo_surface_t *surface; + guint w; + guint h; + guint rowstride; + int frame_width; + double radius; + guint8 *data; + + frame_width = 2; + + w = gdk_pixbuf_get_width (source) + frame_width * 2; + h = gdk_pixbuf_get_height (source) + frame_width * 2; + radius = w / 10; + + dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + TRUE, + 8, + w, + h); + rowstride = gdk_pixbuf_get_rowstride (dest); + + + data = g_new0 (guint8, h * rowstride); + + surface = cairo_image_surface_create_for_data (data, + CAIRO_FORMAT_ARGB32, + w, + h, + rowstride); + cr = cairo_create (surface); + cairo_surface_destroy (surface); + + /* set up image */ + cairo_rectangle (cr, 0, 0, w, h); + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0); + cairo_fill (cr); + + rounded_rectangle (cr, + 1.0, + frame_width + 0.5, + frame_width + 0.5, + radius, + w - frame_width * 2 - 1, + h - frame_width * 2 - 1); + cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.3); + cairo_fill_preserve (cr); + + surface = surface_from_pixbuf (source); + cairo_set_source_surface (cr, surface, frame_width, frame_width); + cairo_fill (cr); + cairo_surface_destroy (surface); + + cairo_to_pixbuf (data, dest); + + cairo_destroy (cr); + g_free (data); + + return dest; +} + +static GdkPixbuf * +render_user_icon (GdmUserChooserWidget *widget, + ActUser *user) +{ + int size; + const char *file; + GdkPixbuf *pixbuf; + GdkPixbuf *framed; + + pixbuf = NULL; + + size = get_icon_height_for_widget (GTK_WIDGET (widget)); + file = act_user_get_icon_file (user); + + if (file) { + pixbuf = gdk_pixbuf_new_from_file_at_size (file, size, size, NULL); + } + + if (pixbuf == NULL) { + GError *error; + + error = NULL; + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + "avatar-default", + size, + GTK_ICON_LOOKUP_FORCE_SIZE, + &error); + if (error != NULL) { + g_warning ("%s", error->message); + g_error_free (error); + } + } + + if (pixbuf != NULL) { + framed = frame_pixbuf (pixbuf); + g_object_unref (pixbuf); + + pixbuf = framed; + } + + return pixbuf; +} + +static void +update_item_for_user (GdmUserChooserWidget *widget, + ActUser *user) +{ + GdkPixbuf *pixbuf; + char *tooltip; + gboolean is_logged_in; + char *escaped_username; + const char *real_name; + char *escaped_real_name; + + if (!act_user_is_loaded (user)) { + return; + } + + pixbuf = render_user_icon (widget, user); + + if (pixbuf == NULL && widget->priv->stock_person_pixbuf != NULL) { + pixbuf = g_object_ref (widget->priv->stock_person_pixbuf); + } + + tooltip = g_strdup_printf (_("Log in as %s"), + act_user_get_user_name (user)); + + is_logged_in = act_user_is_logged_in (user); + + g_debug ("GdmUserChooserWidget: User added name:%s logged-in:%d pixbuf:%p", + act_user_get_user_name (user), + is_logged_in, + pixbuf); + + escaped_username = g_markup_escape_text (act_user_get_user_name (user), -1); + + real_name = act_user_get_real_name (user); + if (real_name == NULL || real_name[0] == '\0') { + real_name = act_user_get_user_name (user); + } + escaped_real_name = g_markup_escape_text (real_name, -1); + + /* Ignore updates we aren't interested in */ + if (!gdm_chooser_widget_lookup_item (GDM_CHOOSER_WIDGET (widget), + escaped_username, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL)) { + return; + } + + gdm_chooser_widget_update_item (GDM_CHOOSER_WIDGET (widget), + escaped_username, + pixbuf, + escaped_real_name, + tooltip, + act_user_get_login_frequency (user), + is_logged_in, + FALSE); + g_free (escaped_real_name); + g_free (escaped_username); + g_free (tooltip); + + if (pixbuf != NULL) { + g_object_unref (pixbuf); + } +} + +static void +on_item_load (GdmChooserWidget *widget, + const char *id, + GdmUserChooserWidget *user_chooser) +{ + ActUser *user; + + g_debug ("GdmUserChooserWidget: Loading item for id=%s", id); + + if (user_chooser->priv->manager == NULL) { + return; + } + + if (strcmp (id, GDM_USER_CHOOSER_USER_OTHER) == 0) { + return; + } + + if (strcmp (id, GDM_USER_CHOOSER_USER_GUEST) == 0) { + return; + } + + user = act_user_manager_get_user (user_chooser->priv->manager, id); + if (user != NULL) { + update_item_for_user (user_chooser, user); + } +} + +static void +add_user_other (GdmUserChooserWidget *widget) +{ + widget->priv->has_user_other = TRUE; + gdm_chooser_widget_add_item (GDM_CHOOSER_WIDGET (widget), + GDM_USER_CHOOSER_USER_OTHER, + NULL, + /* translators: This option prompts + * the user to type in a username + * manually instead of choosing from + * a list. + */ + C_("user", "Other…"), + _("Choose a different account"), + 0, + FALSE, + TRUE, + (GdmChooserWidgetItemLoadFunc) on_item_load, + widget); +} + +static void +add_user_guest (GdmUserChooserWidget *widget) +{ + gdm_chooser_widget_add_item (GDM_CHOOSER_WIDGET (widget), + GDM_USER_CHOOSER_USER_GUEST, + widget->priv->stock_person_pixbuf, + _("Guest"), + _("Log in as a temporary guest"), + 0, + FALSE, + TRUE, + (GdmChooserWidgetItemLoadFunc) on_item_load, + widget); + queue_update_other_user_visibility (widget); +} + +static void +add_user_auto (GdmUserChooserWidget *widget) +{ + gdm_chooser_widget_add_item (GDM_CHOOSER_WIDGET (widget), + GDM_USER_CHOOSER_USER_AUTO, + NULL, + _("Automatic Login"), + _("Automatically log into the system after selecting options"), + 0, + FALSE, + TRUE, + (GdmChooserWidgetItemLoadFunc) on_item_load, + widget); + queue_update_other_user_visibility (widget); +} + +static void +remove_user_other (GdmUserChooserWidget *widget) +{ + widget->priv->has_user_other = FALSE; + gdm_chooser_widget_remove_item (GDM_CHOOSER_WIDGET (widget), + GDM_USER_CHOOSER_USER_OTHER); +} + +static void +remove_user_guest (GdmUserChooserWidget *widget) +{ + gdm_chooser_widget_remove_item (GDM_CHOOSER_WIDGET (widget), + GDM_USER_CHOOSER_USER_GUEST); + queue_update_other_user_visibility (widget); +} + +static void +remove_user_auto (GdmUserChooserWidget *widget) +{ + gdm_chooser_widget_remove_item (GDM_CHOOSER_WIDGET (widget), + GDM_USER_CHOOSER_USER_AUTO); + queue_update_other_user_visibility (widget); +} + +void +gdm_user_chooser_widget_set_show_user_other (GdmUserChooserWidget *widget, + gboolean show_user) +{ + g_return_if_fail (GDM_IS_USER_CHOOSER_WIDGET (widget)); + + if (widget->priv->show_user_other != show_user) { + widget->priv->show_user_other = show_user; + queue_update_other_user_visibility (widget); + } +} + +void +gdm_user_chooser_widget_set_show_user_guest (GdmUserChooserWidget *widget, + gboolean show_user) +{ + g_return_if_fail (GDM_IS_USER_CHOOSER_WIDGET (widget)); + + if (widget->priv->show_user_guest != show_user) { + widget->priv->show_user_guest = show_user; + if (show_user) { + add_user_guest (widget); + } else { + remove_user_guest (widget); + } + } +} + +void +gdm_user_chooser_widget_set_show_user_auto (GdmUserChooserWidget *widget, + gboolean show_user) +{ + g_return_if_fail (GDM_IS_USER_CHOOSER_WIDGET (widget)); + + if (widget->priv->show_user_auto != show_user) { + widget->priv->show_user_auto = show_user; + if (show_user) { + add_user_auto (widget); + } else { + remove_user_auto (widget); + } + } +} + +char * +gdm_user_chooser_widget_get_chosen_user_name (GdmUserChooserWidget *widget) +{ + char *active_item_id; + gboolean isnt_user; + + g_return_val_if_fail (GDM_IS_USER_CHOOSER_WIDGET (widget), NULL); + + active_item_id = gdm_chooser_widget_get_active_item (GDM_CHOOSER_WIDGET (widget)); + if (active_item_id == NULL) { + g_debug ("GdmUserChooserWidget: no active item in list"); + return NULL; + } + + gdm_chooser_widget_lookup_item (GDM_CHOOSER_WIDGET (widget), active_item_id, + NULL, NULL, NULL, NULL, NULL, + &isnt_user); + + if (isnt_user) { + g_debug ("GdmUserChooserWidget: active item '%s' isn't a user", active_item_id); + g_free (active_item_id); + return NULL; + } + + g_debug ("GdmUserChooserWidget: active item '%s' is a user", active_item_id); + + return active_item_id; +} + +void +gdm_user_chooser_widget_set_chosen_user_name (GdmUserChooserWidget *widget, + const char *name) +{ + g_return_if_fail (GDM_IS_USER_CHOOSER_WIDGET (widget)); + + gdm_chooser_widget_set_active_item (GDM_CHOOSER_WIDGET (widget), name); +} + +void +gdm_user_chooser_widget_set_show_only_chosen (GdmUserChooserWidget *widget, + gboolean show_only) { + g_return_if_fail (GDM_IS_USER_CHOOSER_WIDGET (widget)); + + gdm_chooser_widget_set_hide_inactive_items (GDM_CHOOSER_WIDGET (widget), + show_only); + +} +static void +gdm_user_chooser_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmUserChooserWidget *self; + + self = GDM_USER_CHOOSER_WIDGET (object); + + switch (prop_id) { + case PROP_SHOW_USER_AUTO: + gdm_user_chooser_widget_set_show_user_auto (self, g_value_get_boolean (value)); + break; + case PROP_SHOW_USER_GUEST: + gdm_user_chooser_widget_set_show_user_guest (self, g_value_get_boolean (value)); + break; + case PROP_SHOW_USER_OTHER: + gdm_user_chooser_widget_set_show_user_other (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_user_chooser_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmUserChooserWidget *self; + + self = GDM_USER_CHOOSER_WIDGET (object); + + switch (prop_id) { + case PROP_SHOW_USER_AUTO: + g_value_set_boolean (value, self->priv->show_user_auto); + break; + case PROP_SHOW_USER_GUEST: + g_value_set_boolean (value, self->priv->show_user_guest); + break; + case PROP_SHOW_USER_OTHER: + g_value_set_boolean (value, self->priv->show_user_other); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +is_user_list_disabled (GdmUserChooserWidget *widget) +{ + GSettings *settings; + gboolean result; + + settings = g_settings_new (LOGIN_SCREEN_SCHEMA); + result = g_settings_get_boolean (settings, KEY_DISABLE_USER_LIST); + g_object_unref (settings); + + return result; +} + +static void +add_user (GdmUserChooserWidget *widget, + ActUser *user) +{ + GdkPixbuf *pixbuf; + char *tooltip; + gboolean is_logged_in; + char *escaped_username; + const char *real_name; + char *escaped_real_name; + + if (!widget->priv->show_normal_users) { + return; + } + + if (strcmp (act_user_get_user_name (user), GDM_USERNAME) == 0) { + return; + } + + if (act_user_get_uid (user) == 0) { + return; + } + + if (act_user_get_locked (user)) { + g_debug ("GdmUserChooserWidget: Skipping locked user: %s", act_user_get_user_name (user)); + return; + } + + g_debug ("GdmUserChooserWidget: User added: %s", act_user_get_user_name (user)); + if (widget->priv->stock_person_pixbuf != NULL) { + pixbuf = g_object_ref (widget->priv->stock_person_pixbuf); + } else { + pixbuf = NULL; + } + + tooltip = g_strdup_printf (_("Log in as %s"), + act_user_get_user_name (user)); + + is_logged_in = act_user_is_logged_in (user); + + escaped_username = g_markup_escape_text (act_user_get_user_name (user), -1); + real_name = act_user_get_real_name (user); + if (real_name == NULL || real_name[0] == '\0') { + real_name = act_user_get_user_name (user); + } + escaped_real_name = g_markup_escape_text (real_name, -1); + + gdm_chooser_widget_add_item (GDM_CHOOSER_WIDGET (widget), + escaped_username, + pixbuf, + escaped_real_name, + tooltip, + act_user_get_login_frequency (user), + is_logged_in, + FALSE, + (GdmChooserWidgetItemLoadFunc) on_item_load, + widget); + g_free (escaped_real_name); + g_free (escaped_username); + g_free (tooltip); + + if (pixbuf != NULL) { + g_object_unref (pixbuf); + } + + queue_update_other_user_visibility (widget); +} + +static void +on_user_added (ActUserManager *manager, + ActUser *user, + GdmUserChooserWidget *widget) +{ + /* wait for all users to be loaded */ + if (! widget->priv->loaded) { + return; + } + add_user (widget, user); +} + +static void +on_user_removed (ActUserManager *manager, + ActUser *user, + GdmUserChooserWidget *widget) +{ + const char *user_name; + + g_debug ("GdmUserChooserWidget: User removed: %s", act_user_get_user_name (user)); + /* wait for all users to be loaded */ + if (! widget->priv->loaded) { + return; + } + + user_name = act_user_get_user_name (user); + + /* Ignore removals we aren't interested in */ + if (!gdm_chooser_widget_lookup_item (GDM_CHOOSER_WIDGET (widget), + user_name, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL)) { + return; + } + + gdm_chooser_widget_remove_item (GDM_CHOOSER_WIDGET (widget), + user_name); + + queue_update_other_user_visibility (widget); +} + +static void +on_user_is_logged_in_changed (ActUserManager *manager, + ActUser *user, + GdmUserChooserWidget *widget) +{ + const char *user_name; + gboolean is_logged_in; + + g_debug ("GdmUserChooserWidget: User logged in changed: %s", act_user_get_user_name (user)); + + user_name = act_user_get_user_name (user); + is_logged_in = act_user_is_logged_in (user); + + gdm_chooser_widget_set_item_in_use (GDM_CHOOSER_WIDGET (widget), + user_name, + is_logged_in); +} + +static void +on_user_changed (ActUserManager *manager, + ActUser *user, + GdmUserChooserWidget *widget) +{ + /* wait for all users to be loaded */ + if (! widget->priv->loaded) { + return; + } + if (! widget->priv->show_normal_users) { + return; + } + + update_item_for_user (widget, user); +} + +static gboolean +add_users (GdmUserChooserWidget *widget) +{ + guint cnt; + + cnt = 0; + while (widget->priv->users_to_add != NULL && cnt < NUM_USERS_TO_ADD_PER_ITERATION) { + add_user (widget, widget->priv->users_to_add->data); + g_object_unref (widget->priv->users_to_add->data); + widget->priv->users_to_add = g_slist_delete_link (widget->priv->users_to_add, widget->priv->users_to_add); + cnt++; + } + g_debug ("GdmUserChooserWidget: added %u items", cnt); + + if (! widget->priv->loaded) { + widget->priv->loaded = TRUE; + + gdm_chooser_widget_loaded (GDM_CHOOSER_WIDGET (widget)); + } + + if (widget->priv->users_to_add == NULL) { + widget->priv->add_users_idle_id = 0; + return FALSE; + } + + return TRUE; +} + +static void +queue_add_users (GdmUserChooserWidget *widget) +{ + if (widget->priv->add_users_idle_id == 0) { + widget->priv->add_users_idle_id = g_idle_add ((GSourceFunc) add_users, widget); + } +} + +static void +on_is_loaded_changed (ActUserManager *manager, + GParamSpec *pspec, + GdmUserChooserWidget *widget) +{ + GSList *users; + + /* FIXME: handle is-loaded=FALSE */ + + g_debug ("GdmUserChooserWidget: Users loaded"); + + users = act_user_manager_list_users (manager); + g_slist_foreach (users, (GFunc) g_object_ref, NULL); + widget->priv->users_to_add = g_slist_concat (widget->priv->users_to_add, g_slist_copy (users)); + + queue_add_users (widget); +} + +static gboolean +load_users (GdmUserChooserWidget *widget) +{ + + if (widget->priv->show_normal_users) { + widget->priv->manager = act_user_manager_get_default (); + + g_signal_connect (widget->priv->manager, + "user-added", + G_CALLBACK (on_user_added), + widget); + g_signal_connect (widget->priv->manager, + "user-removed", + G_CALLBACK (on_user_removed), + widget); + g_signal_connect (widget->priv->manager, + "notify::is-loaded", + G_CALLBACK (on_is_loaded_changed), + widget); + g_signal_connect (widget->priv->manager, + "user-is-logged-in-changed", + G_CALLBACK (on_user_is_logged_in_changed), + widget); + g_signal_connect (widget->priv->manager, + "user-changed", + G_CALLBACK (on_user_changed), + widget); + } else { + gdm_chooser_widget_loaded (GDM_CHOOSER_WIDGET (widget)); + } + + widget->priv->load_idle_id = 0; + + return FALSE; +} + +static GObject * +gdm_user_chooser_widget_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GdmUserChooserWidget *widget; + + widget = GDM_USER_CHOOSER_WIDGET (G_OBJECT_CLASS (gdm_user_chooser_widget_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + widget->priv->show_normal_users = !is_user_list_disabled (widget); + + widget->priv->load_idle_id = g_idle_add ((GSourceFunc)load_users, widget); + + return G_OBJECT (widget); +} + +static void +gdm_user_chooser_widget_dispose (GObject *object) +{ + GdmUserChooserWidget *widget; + + widget = GDM_USER_CHOOSER_WIDGET (object); + + G_OBJECT_CLASS (gdm_user_chooser_widget_parent_class)->dispose (object); + + if (widget->priv->load_idle_id > 0) { + g_source_remove (widget->priv->load_idle_id); + widget->priv->load_idle_id = 0; + } + + if (widget->priv->add_users_idle_id > 0) { + g_source_remove (widget->priv->add_users_idle_id); + widget->priv->add_users_idle_id = 0; + } + + if (widget->priv->update_other_visibility_idle_id > 0) { + g_source_remove (widget->priv->update_other_visibility_idle_id); + widget->priv->update_other_visibility_idle_id = 0; + } + + if (widget->priv->users_to_add != NULL) { + g_slist_foreach (widget->priv->users_to_add, (GFunc) g_object_ref, NULL); + g_slist_free (widget->priv->users_to_add); + widget->priv->users_to_add = NULL; + } + + if (widget->priv->logged_in_pixbuf != NULL) { + g_object_unref (widget->priv->logged_in_pixbuf); + widget->priv->logged_in_pixbuf = NULL; + } + + if (widget->priv->stock_person_pixbuf != NULL) { + g_object_unref (widget->priv->stock_person_pixbuf); + widget->priv->stock_person_pixbuf = NULL; + } + + if (widget->priv->manager != NULL) { + g_signal_handlers_disconnect_by_func (widget->priv->manager, + G_CALLBACK (on_user_added), + widget); + g_signal_handlers_disconnect_by_func (widget->priv->manager, + G_CALLBACK (on_user_removed), + widget); + g_signal_handlers_disconnect_by_func (widget->priv->manager, + G_CALLBACK (on_is_loaded_changed), + widget); + g_signal_handlers_disconnect_by_func (widget->priv->manager, + G_CALLBACK (on_user_is_logged_in_changed), + widget); + g_signal_handlers_disconnect_by_func (widget->priv->manager, + G_CALLBACK (on_user_changed), + widget); + + g_object_unref (widget->priv->manager); + widget->priv->manager = NULL; + } +} + +static void +gdm_user_chooser_widget_class_init (GdmUserChooserWidgetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gdm_user_chooser_widget_get_property; + object_class->set_property = gdm_user_chooser_widget_set_property; + object_class->constructor = gdm_user_chooser_widget_constructor; + object_class->dispose = gdm_user_chooser_widget_dispose; + object_class->finalize = gdm_user_chooser_widget_finalize; + + + g_object_class_install_property (object_class, + PROP_SHOW_USER_AUTO, + g_param_spec_boolean ("show-user-auto", + "show user auto", + "show user auto", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_SHOW_USER_GUEST, + g_param_spec_boolean ("show-user-guest", + "show user guest", + "show user guest", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_SHOW_USER_OTHER, + g_param_spec_boolean ("show-user-other", + "show user other", + "show user other", + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GdmUserChooserWidgetPrivate)); +} + +static GdkPixbuf * +get_pixbuf_from_icon_names (GdmUserChooserWidget *widget, + const char *first_name, + ...) +{ + GdkPixbuf *pixbuf; + GtkIconInfo *icon_info; + GPtrArray *array; + int size; + const char *icon_name; + va_list argument_list; + gint *sizes; + gint candidate_size; + int i; + + array = g_ptr_array_new (); + + g_ptr_array_add (array, (gpointer) first_name); + + va_start (argument_list, first_name); + icon_name = (const char *) va_arg (argument_list, const char *); + while (icon_name != NULL) { + g_ptr_array_add (array, (gpointer) icon_name); + icon_name = (const char *) va_arg (argument_list, const char *); + } + va_end (argument_list); + g_ptr_array_add (array, NULL); + + size = get_icon_height_for_widget (GTK_WIDGET (widget)); + + sizes = gtk_icon_theme_get_icon_sizes (widget->priv->icon_theme, first_name); + + candidate_size = 0; + for (i = 0; sizes[i] != 0; i++) { + + /* scalable */ + if (sizes[i] == -1) { + candidate_size = sizes[i]; + break; + } + + if (ABS (size - sizes[i]) < ABS (size - candidate_size)) { + candidate_size = sizes[i]; + } + } + + if (candidate_size == 0) { + candidate_size = size; + } + + icon_info = gtk_icon_theme_choose_icon (widget->priv->icon_theme, + (const char **) array->pdata, + candidate_size, + GTK_ICON_LOOKUP_GENERIC_FALLBACK); + g_ptr_array_free (array, FALSE); + + if (icon_info != NULL) { + GError *error; + + error = NULL; + pixbuf = gtk_icon_info_load_icon (icon_info, &error); + gtk_icon_info_free (icon_info); + + if (error != NULL) { + g_warning ("Could not load icon '%s': %s", + first_name, error->message); + g_error_free (error); + } + } else { + GdkPixbuf *scaled_pixbuf; + + guchar pixel = 0x00000000; + + g_warning ("Could not find icon '%s' or fallbacks", first_name); + pixbuf = gdk_pixbuf_new_from_data (&pixel, GDK_COLORSPACE_RGB, + TRUE, 8, 1, 1, 1, NULL, NULL); + scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf, size, size, GDK_INTERP_NEAREST); + g_object_unref (pixbuf); + pixbuf = scaled_pixbuf; + } + + return pixbuf; +} + +static GdkPixbuf * +get_stock_person_pixbuf (GdmUserChooserWidget *widget) +{ + GdkPixbuf *pixbuf; + + pixbuf = get_pixbuf_from_icon_names (widget, + DEFAULT_USER_ICON, + OLD_DEFAULT_USER_ICON, + NULL); + + return pixbuf; +} + +static GdkPixbuf * +get_logged_in_pixbuf (GdmUserChooserWidget *widget) +{ + GdkPixbuf *pixbuf; + + pixbuf = get_pixbuf_from_icon_names (widget, + DEFAULT_USER_ICON, + "emblem-default", + NULL); + + return pixbuf; +} + +typedef struct { + GdkPixbuf *old_icon; + GdkPixbuf *new_icon; +} IconUpdateData; + +static gboolean +update_icons (GdmChooserWidget *widget, + const char *id, + GdkPixbuf **image, + char **name, + char **comment, + gulong *priority, + gboolean *is_in_use, + gboolean *is_separate, + IconUpdateData *data) +{ + if (data->old_icon == *image) { + if (*image != NULL) { + g_object_unref (*image); + } + *image = data->new_icon; + + if (*image != NULL) { + g_object_ref (*image); + } + return TRUE; + } + + return FALSE; +} + +static void +load_icons (GdmUserChooserWidget *widget) +{ + GdkPixbuf *old_pixbuf; + IconUpdateData data; + + if (widget->priv->logged_in_pixbuf != NULL) { + g_object_unref (widget->priv->logged_in_pixbuf); + } + widget->priv->logged_in_pixbuf = get_logged_in_pixbuf (widget); + + old_pixbuf = widget->priv->stock_person_pixbuf; + widget->priv->stock_person_pixbuf = get_stock_person_pixbuf (widget); + /* update the icons in the model */ + data.old_icon = old_pixbuf; + data.new_icon = widget->priv->stock_person_pixbuf; + gdm_chooser_widget_update_foreach_item (GDM_CHOOSER_WIDGET (widget), + (GdmChooserUpdateForeachFunc)update_icons, + &data); + if (old_pixbuf != NULL) { + g_object_unref (old_pixbuf); + } +} + +static void +on_icon_theme_changed (GtkIconTheme *icon_theme, + GdmUserChooserWidget *widget) +{ + g_debug ("GdmUserChooserWidget: icon theme changed"); + load_icons (widget); +} + +static void +setup_icons (GdmUserChooserWidget *widget) +{ + if (gtk_widget_has_screen (GTK_WIDGET (widget))) { + widget->priv->icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (widget))); + } else { + widget->priv->icon_theme = gtk_icon_theme_get_default (); + } + + if (widget->priv->icon_theme != NULL) { + g_signal_connect (widget->priv->icon_theme, + "changed", + G_CALLBACK (on_icon_theme_changed), + widget); + } + + load_icons (widget); +} + +static void +on_list_visible_changed (GdmChooserWidget *widget, + GParamSpec *pspec, + gpointer data) +{ + gboolean is_visible; + + g_object_get (G_OBJECT (widget), "list-visible", &is_visible, NULL); + if (is_visible) { + gtk_widget_grab_focus (GTK_WIDGET (widget)); + } +} + +static void +gdm_user_chooser_widget_init (GdmUserChooserWidget *widget) +{ + widget->priv = GDM_USER_CHOOSER_WIDGET_GET_PRIVATE (widget); + + gdm_chooser_widget_set_separator_position (GDM_CHOOSER_WIDGET (widget), + GDM_CHOOSER_WIDGET_POSITION_BOTTOM); + gdm_chooser_widget_set_in_use_message (GDM_CHOOSER_WIDGET (widget), + _("Currently logged in")); + + g_signal_connect (widget, + "notify::list-visible", + G_CALLBACK (on_list_visible_changed), + NULL); + + setup_icons (widget); +} + +static void +gdm_user_chooser_widget_finalize (GObject *object) +{ + GdmUserChooserWidget *widget; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_USER_CHOOSER_WIDGET (object)); + + widget = GDM_USER_CHOOSER_WIDGET (object); + + g_return_if_fail (widget->priv != NULL); + + G_OBJECT_CLASS (gdm_user_chooser_widget_parent_class)->finalize (object); +} + +GtkWidget * +gdm_user_chooser_widget_new (void) +{ + GObject *object; + + object = g_object_new (GDM_TYPE_USER_CHOOSER_WIDGET, + NULL); + + return GTK_WIDGET (object); +} diff --git a/gui/simple-greeter/gdm-user-chooser-widget.h b/gui/simple-greeter/gdm-user-chooser-widget.h new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef __GDM_USER_CHOOSER_WIDGET_H +#define __GDM_USER_CHOOSER_WIDGET_H + +#include <glib-object.h> + +#include "gdm-chooser-widget.h" + +G_BEGIN_DECLS + +#define GDM_TYPE_USER_CHOOSER_WIDGET (gdm_user_chooser_widget_get_type ()) +#define GDM_USER_CHOOSER_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_USER_CHOOSER_WIDGET, GdmUserChooserWidget)) +#define GDM_USER_CHOOSER_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_USER_CHOOSER_WIDGET, GdmUserChooserWidgetClass)) +#define GDM_IS_USER_CHOOSER_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_USER_CHOOSER_WIDGET)) +#define GDM_IS_USER_CHOOSER_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDM_TYPE_USER_CHOOSER_WIDGET)) +#define GDM_USER_CHOOSER_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_USER_CHOOSER_WIDGET, GdmUserChooserWidgetClass)) + +typedef struct GdmUserChooserWidgetPrivate GdmUserChooserWidgetPrivate; + +typedef struct +{ + GdmChooserWidget parent; + GdmUserChooserWidgetPrivate *priv; +} GdmUserChooserWidget; + +typedef struct +{ + GdmChooserWidgetClass parent_class; +} GdmUserChooserWidgetClass; + +#define GDM_USER_CHOOSER_USER_OTHER "__other" +#define GDM_USER_CHOOSER_USER_GUEST "__guest" +#define GDM_USER_CHOOSER_USER_AUTO "__auto" + +GType gdm_user_chooser_widget_get_type (void); +GtkWidget * gdm_user_chooser_widget_new (void); + +char * gdm_user_chooser_widget_get_chosen_user_name (GdmUserChooserWidget *widget); +void gdm_user_chooser_widget_set_chosen_user_name (GdmUserChooserWidget *widget, + const char *user_name); +void gdm_user_chooser_widget_set_show_only_chosen (GdmUserChooserWidget *widget, + gboolean show_only); +void gdm_user_chooser_widget_set_show_user_other (GdmUserChooserWidget *widget, + gboolean show); +void gdm_user_chooser_widget_set_show_user_guest (GdmUserChooserWidget *widget, + gboolean show); +void gdm_user_chooser_widget_set_show_user_auto (GdmUserChooserWidget *widget, + gboolean show); +G_END_DECLS + +#endif /* __GDM_USER_CHOOSER_WIDGET_H */ diff --git a/gui/simple-greeter/gdm-user-private.h b/gui/simple-greeter/gdm-user-private.h new file mode 100644 index 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 <jcape@ignore-your.tv>. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * Private interfaces to the GdmUser object + */ + +#ifndef __GDM_USER_PRIVATE_H_ +#define __GDM_USER_PRIVATE_H_ + +#include <pwd.h> + +#include "gdm-user.h" + +G_BEGIN_DECLS + +void _gdm_user_update_from_object_path (GdmUser *user, + const char *object_path); + +void _gdm_user_update_from_pwent (GdmUser *user, + const struct passwd *pwent); + +void _gdm_user_update_login_frequency (GdmUser *user, + guint64 login_frequency); + +void _gdm_user_add_session (GdmUser *user, + const char *session_id); +void _gdm_user_remove_session (GdmUser *user, + const char *session_id); + +G_END_DECLS + +#endif /* !__GDM_USER_PRIVATE__ */ diff --git a/gui/simple-greeter/greeter-main.c b/gui/simple-greeter/greeter-main.c new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include <stdlib.h> +#include <locale.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> + +#include "gdm-log.h" +#include "gdm-common.h" +#include "gdm-settings-client.h" +#include "gdm-settings-keys.h" +#include "gdm-profile.h" + +#include "gdm-greeter-session.h" + +#include "gsm-client-glue.h" +#include "gsm-manager-glue.h" + +#define SM_DBUS_NAME "org.gnome.SessionManager" +#define SM_DBUS_PATH "/org/gnome/SessionManager" +#define SM_DBUS_INTERFACE "org.gnome.SessionManager" + +#define SM_CLIENT_DBUS_INTERFACE "org.gnome.SessionManager.ClientPrivate" + +static GDBusConnection *bus_connection = NULL; +static GsmManager *sm_proxy = NULL; +static char *client_id = NULL; +static GsmClientPrivate *client_proxy = NULL; + +static gboolean +is_debug_set (void) +{ + gboolean debug = FALSE; + + /* enable debugging for unstable builds */ + if (gdm_is_version_unstable ()) { + return TRUE; + } + + gdm_settings_client_get_boolean (GDM_KEY_DEBUG, &debug); + return debug; +} + +static gboolean +on_sigusr1_cb (gpointer user_data) +{ + g_debug ("Got USR1 signal"); + + gdm_log_toggle_debug (); + + return TRUE; +} + +static gboolean +session_manager_connect (void) +{ + GError *error; + + error = NULL; + + if (bus_connection == NULL) { + bus_connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (bus_connection == NULL) { + g_message ("Failed to connect to the session bus: %s", + error->message); + g_error_free (error); + exit (1); + } + + g_signal_connect (G_OBJECT (bus_connection), + "closed", + G_CALLBACK (gtk_main_quit), + NULL); + } + + sm_proxy = gsm_manager_proxy_new_sync (bus_connection, + G_DBUS_PROXY_FLAGS_NONE, + SM_DBUS_NAME, + SM_DBUS_PATH, + NULL, + &error); + + if (sm_proxy == NULL) { + g_message ("Failed to connect to the session manager: %s", + error->message); + g_error_free (error); + } + + return (sm_proxy != NULL); +} + +static void +stop_cb (GsmClientPrivate *client_private, + gpointer data) +{ + gtk_main_quit (); +} + +static gboolean +end_session_response (gboolean is_okay, const gchar *reason) +{ + gboolean ret; + GError *error = NULL; + + if (reason == NULL) { + reason = ""; + } + + ret = gsm_client_private_call_end_session_response_sync (client_proxy, is_okay, reason, NULL, &error); + + if (!ret) { + g_warning ("Failed to send session response %s", error->message); + g_error_free (error); + } + + return ret; +} + +static void +query_end_session_cb (GsmClientPrivate *client_private, + guint flags, + gpointer data) +{ + end_session_response (TRUE, NULL); +} + +static void +end_session_cb (guint flags, gpointer data) +{ + end_session_response (TRUE, NULL); + gtk_main_quit (); +} + +static gboolean +register_client (void) +{ + GError *error; + gboolean res; + const char *startup_id; + const char *app_id; + + startup_id = g_getenv ("DESKTOP_AUTOSTART_ID"); + app_id = "gdm-simple-greeter.desktop"; + + error = NULL; + res = gsm_manager_call_register_client_sync (sm_proxy, app_id, startup_id, &client_id, NULL, &error); + if (! res) { + g_warning ("Failed to register client: %s", error->message); + g_error_free (error); + return FALSE; + } + + g_debug ("Client registered with session manager: %s", client_id); + client_proxy = gsm_client_private_proxy_new_sync (bus_connection, + G_DBUS_PROXY_FLAGS_NONE, + SM_DBUS_NAME, + client_id, + NULL, + &error); + + if (client_proxy == NULL) { + g_warning ("Failed to track client: %s", error->message); + g_error_free (error); + + return FALSE; + } + + g_signal_connect (client_proxy, + "stop", + G_CALLBACK (stop_cb), + NULL); + + g_signal_connect (client_proxy, + "query-end-session", + G_CALLBACK (query_end_session_cb), + NULL); + + g_signal_connect (client_proxy, + "end-session", + G_CALLBACK (end_session_cb), + NULL); + + g_unsetenv ("DESKTOP_AUTOSTART_ID"); + + return TRUE; +} + +int +main (int argc, char *argv[]) +{ + GError *error; + GdmGreeterSession *session; + gboolean res; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + setlocale (LC_ALL, ""); + + gdm_profile_start ("Initializing settings client"); + if (! gdm_settings_client_init (DATADIR "/gdm/gdm.schemas", "/")) { + g_critical ("Unable to initialize settings client"); + exit (1); + } + gdm_profile_end ("Initializing settings client"); + + g_debug ("Greeter session pid=%d display=%s xauthority=%s", + (int)getpid (), + g_getenv ("DISPLAY"), + g_getenv ("XAUTHORITY")); + + /* FIXME: For testing to make it easier to attach gdb */ + /*sleep (15);*/ + + gdm_log_init (); + gdm_log_set_debug (is_debug_set ()); + + gtk_init (&argc, &argv); + + g_unix_signal_add (SIGUSR1, on_sigusr1_cb, NULL); + + gdm_profile_start ("Creating new greeter session"); + session = gdm_greeter_session_new (); + if (session == NULL) { + g_critical ("Unable to create greeter session"); + exit (1); + } + gdm_profile_end ("Creating new greeter session"); + + error = NULL; + res = gdm_greeter_session_start (session, &error); + if (! res) { + if (error != NULL) { + g_warning ("Unable to start greeter session: %s", error->message); + g_error_free (error); + } + exit (1); + } + + res = session_manager_connect (); + if (! res) { + g_warning ("Unable to connect to session manager"); + exit (1); + } + + res = register_client (); + if (! res) { + g_warning ("Unable to register client with session manager"); + } + + gtk_main (); + + if (session != NULL) { + g_object_unref (session); + } + + return 0; +} diff --git a/gui/simple-greeter/libgdmsimplegreeter/Makefile.am b/gui/simple-greeter/libgdmsimplegreeter/Makefile.am new file mode 100644 index 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 <rstrode@redhat.com> + * + */ + +#include <glib.h> +#include <glib-object.h> + +#include "gdm-login-extension.h" + +enum { + ENABLED, + DISABLED, + ANSWER, + USER_CHOSEN, + CANCEL, + MESSAGE_QUEUE_EMPTY, + NUMBER_OF_SIGNALS +}; + +static guint signals [NUMBER_OF_SIGNALS] = { 0, }; + +static void gdm_login_extension_class_init (gpointer g_iface); + +GType +gdm_login_extension_get_type (void) +{ + static GType login_extension_type = 0; + + if (!login_extension_type) { + login_extension_type = g_type_register_static_simple (G_TYPE_INTERFACE, + "GdmLoginExtension", + sizeof (GdmLoginExtensionIface), + (GClassInitFunc) gdm_login_extension_class_init, + 0, NULL, 0); + + g_type_interface_add_prerequisite (login_extension_type, G_TYPE_OBJECT); + } + + return login_extension_type; +} + +static void +gdm_login_extension_class_init (gpointer g_iface) +{ + GType iface_type = G_TYPE_FROM_INTERFACE (g_iface); + + signals [ENABLED] = + g_signal_new ("enabled", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmLoginExtensionIface, enabled), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + signals [DISABLED] = + g_signal_new ("disabled", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmLoginExtensionIface, disabled), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + signals [ANSWER] = + g_signal_new ("answer", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmLoginExtensionIface, answer), + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, G_TYPE_STRING); + signals [USER_CHOSEN] = + g_signal_new ("user-chosen", + iface_type, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GdmLoginExtensionIface, user_chosen), + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, + 1, G_TYPE_STRING); + signals [CANCEL] = + g_signal_new ("cancel", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmLoginExtensionIface, cancel), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals [MESSAGE_QUEUE_EMPTY] = + g_signal_new ("message-queue-empty", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GdmLoginExtensionIface, message_queue_empty), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +GIcon * +gdm_login_extension_get_icon (GdmLoginExtension *extension) +{ + return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_icon (extension); +} + +char * +gdm_login_extension_get_description (GdmLoginExtension *extension) +{ + return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_description (extension); +} + +char * +gdm_login_extension_get_name (GdmLoginExtension *extension) +{ + return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_name (extension); +} + +void +gdm_login_extension_set_enabled (GdmLoginExtension *extension, + gboolean should_enable) +{ + g_object_set_data (G_OBJECT (extension), + "gdm-greeter-extension-is-disabled", + GINT_TO_POINTER (!should_enable)); + + if (should_enable) { + g_signal_emit (G_OBJECT (extension), signals [ENABLED], 0); + } else { + g_signal_emit (G_OBJECT (extension), signals [DISABLED], 0); + } +} + +gboolean +gdm_login_extension_is_enabled (GdmLoginExtension *extension) +{ + return !g_object_get_data (G_OBJECT (extension), "gdm-greeter-extension-is-disabled"); +} + +gboolean +gdm_login_extension_is_choosable (GdmLoginExtension *extension) +{ + return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->is_choosable (extension); +} + +gboolean +gdm_login_extension_is_visible (GdmLoginExtension *extension) +{ + return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->is_visible (extension); +} + +void +gdm_login_extension_queue_message (GdmLoginExtension *extension, + GdmServiceMessageType type, + const char *message) +{ + GDM_LOGIN_EXTENSION_GET_IFACE (extension)->queue_message (extension, + type, + message); +} + +void +gdm_login_extension_ask_question (GdmLoginExtension *extension, + const char *message) +{ + GDM_LOGIN_EXTENSION_GET_IFACE (extension)->ask_question (extension, + message); +} + +void +gdm_login_extension_ask_secret (GdmLoginExtension *extension, + const char *message) +{ + GDM_LOGIN_EXTENSION_GET_IFACE (extension)->ask_secret (extension, + message); + +} + +void +gdm_login_extension_reset (GdmLoginExtension *extension) +{ + GDM_LOGIN_EXTENSION_GET_IFACE (extension)->reset (extension); +} + +void +gdm_login_extension_set_ready (GdmLoginExtension *extension) +{ + GDM_LOGIN_EXTENSION_GET_IFACE (extension)->set_ready (extension); +} + +gboolean +gdm_login_extension_focus (GdmLoginExtension *extension) +{ + return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->focus (extension); +} + +char * +gdm_login_extension_get_service_name (GdmLoginExtension *extension) +{ + return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_service_name (extension); + +} + +gboolean +gdm_login_extension_has_queued_messages (GdmLoginExtension *extension) +{ + return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->has_queued_messages (extension); +} + +GtkWidget * +gdm_login_extension_get_page (GdmLoginExtension *extension) +{ + return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_page (extension); +} + +GtkActionGroup * +gdm_login_extension_get_actions (GdmLoginExtension *extension) +{ + return GDM_LOGIN_EXTENSION_GET_IFACE (extension)->get_actions (extension); +} + +void +_gdm_login_extension_emit_answer (GdmLoginExtension *extension, + const char *answer) +{ + g_signal_emit (extension, signals [ANSWER], 0, answer); + +} + +void +_gdm_login_extension_emit_cancel (GdmLoginExtension *extension) +{ + g_signal_emit (extension, signals [CANCEL], 0); +} + +gboolean +_gdm_login_extension_emit_choose_user (GdmLoginExtension *extension, + const char *username) +{ + gboolean was_chosen; + + was_chosen = FALSE; + + g_signal_emit (extension, signals [USER_CHOSEN], 0, username, &was_chosen); + + return was_chosen; +} + +void +_gdm_login_extension_emit_message_queue_empty (GdmLoginExtension *extension) +{ + g_signal_emit (extension, signals [MESSAGE_QUEUE_EMPTY], 0); + +} diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.h b/gui/simple-greeter/libgdmsimplegreeter/gdm-login-extension.h new file mode 100644 index 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 <gio/gio.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GDM_TYPE_LOGIN_EXTENSION (gdm_login_extension_get_type ()) +#define GDM_LOGIN_EXTENSION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDM_TYPE_LOGIN_EXTENSION, GdmLoginExtension)) +#define GDM_LOGIN_EXTENSION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDM_TYPE_LOGIN_EXTENSION, GdmLoginExtensionClass)) +#define GDM_IS_LOGIN_EXTENSION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDM_TYPE_LOGIN_EXTENSION)) +#define GDM_LOGIN_EXTENSION_GET_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), GDM_TYPE_LOGIN_EXTENSION, GdmLoginExtensionIface)) + +#define GDM_LOGIN_EXTENSION_POINT_NAME "gdm-login" +#define GDM_LOGIN_EXTENSION_DEFAULT_ACTION "default-action" +#define GDM_LOGIN_EXTENSION_OTHER_USER "__other" + +typedef struct _GdmLoginExtension GdmLoginExtension; +typedef struct _GdmLoginExtensionIface GdmLoginExtensionIface; + +typedef enum { + GDM_SERVICE_MESSAGE_TYPE_INFO, + GDM_SERVICE_MESSAGE_TYPE_PROBLEM +} GdmServiceMessageType; + +struct _GdmLoginExtensionIface +{ + GTypeInterface base_iface; + + /* methods */ + GIcon * (* get_icon) (GdmLoginExtension *extension); + char * (* get_description) (GdmLoginExtension *extension); + char * (* get_name) (GdmLoginExtension *extension); + + gboolean (* is_choosable) (GdmLoginExtension *extension); + gboolean (* is_visible) (GdmLoginExtension *extension); + + void (* queue_message) (GdmLoginExtension *extension, + GdmServiceMessageType type, + const char *message); + void (* ask_question) (GdmLoginExtension *extension, + const char *message); + void (* ask_secret) (GdmLoginExtension *extension, + const char *message); + void (* reset) (GdmLoginExtension *extension); + void (* set_ready) (GdmLoginExtension *extension); + char * (* get_service_name) (GdmLoginExtension *extension); + GtkWidget * (* get_page) (GdmLoginExtension *extension); + GtkActionGroup * (* get_actions) (GdmLoginExtension *extension); + void (* request_answer) (GdmLoginExtension *extension); + gboolean (* has_queued_messages) (GdmLoginExtension *extension); + gboolean (* focus) (GdmLoginExtension *extension); + + /* signals */ + void (* enabled) (GdmLoginExtension *extension); + void (* disabled) (GdmLoginExtension *extension); + char * (* answer) (GdmLoginExtension *extension); + void (* cancel) (GdmLoginExtension *extension); + gboolean (* user_chosen) (GdmLoginExtension *extension); + gboolean (* message_queue_empty) (GdmLoginExtension *extension); +}; + +GType gdm_login_extension_get_type (void) G_GNUC_CONST; + +GIcon *gdm_login_extension_get_icon (GdmLoginExtension *extension); +char *gdm_login_extension_get_description (GdmLoginExtension *extension); +char *gdm_login_extension_get_name (GdmLoginExtension *extension); +void gdm_login_extension_set_enabled (GdmLoginExtension *extension, + gboolean should_enable); +gboolean gdm_login_extension_is_enabled (GdmLoginExtension *extension); +gboolean gdm_login_extension_is_choosable (GdmLoginExtension *extension); +gboolean gdm_login_extension_is_visible (GdmLoginExtension *extension); + +void gdm_login_extension_queue_message (GdmLoginExtension *extension, + GdmServiceMessageType type, + const char *message); + +void gdm_login_extension_ask_question (GdmLoginExtension *extension, + const char *message); + +void gdm_login_extension_ask_secret (GdmLoginExtension *extension, + const char *message); + +void gdm_login_extension_reset (GdmLoginExtension *extension); +void gdm_login_extension_set_ready (GdmLoginExtension *extension); +gboolean gdm_login_extension_focus (GdmLoginExtension *extension); + +char *gdm_login_extension_get_service_name (GdmLoginExtension *extension); +gboolean gdm_login_extension_has_queued_messages (GdmLoginExtension *extension); + +GtkWidget *gdm_login_extension_get_page (GdmLoginExtension *extension); +GtkActionGroup *gdm_login_extension_get_actions (GdmLoginExtension *extension); + + +/* protected + */ +void _gdm_login_extension_emit_answer (GdmLoginExtension *extension, + const char *answer); +void _gdm_login_extension_emit_cancel (GdmLoginExtension *extension); +gboolean _gdm_login_extension_emit_choose_user (GdmLoginExtension *extension, + const char *username); + +void _gdm_login_extension_emit_message_queue_empty (GdmLoginExtension *extension); + + +G_END_DECLS +#endif /* __GDM_LOGIN_EXTENSION_H */ diff --git a/gui/simple-greeter/libgdmsimplegreeter/gdmsimplegreeter.pc.in b/gui/simple-greeter/libgdmsimplegreeter/gdmsimplegreeter.pc.in new file mode 100644 index 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 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> + <interface name="org.gnome.SessionManager.ClientPrivate"> + <method name="EndSessionResponse"> + <arg name="is_ok" type="b" direction="in"> + <doc:doc> + <doc:summary>Whether or not it is OK to preceed</doc:summary> + </doc:doc> + </arg> + <arg name="reason" type="s" direction="in"> + <doc:doc> + <doc:summary>The reason string</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This method should be used by the client in response to + the <doc:ref type="signal" to="org.gnome.SessionManager.ClientPrivate::QueryEndSession">QueryEndSession</doc:ref> + and <doc:ref type="signal" to="org.gnome.SessionManager.ClientPrivate::EndSession">EndSession</doc:ref> signals. + </doc:para> + </doc:description> + </doc:doc> + </method> + + <signal name="Stop"> + <doc:doc> + <doc:summary>Stop client</doc:summary> + <doc:description> + <doc:para> + The client should stop and remove itself from the session in + response to this signal. + </doc:para> + </doc:description> + </doc:doc> + </signal> + + <signal name="QueryEndSession"> + <arg name="flags" type="u"> + <doc:doc> + <doc:summary>Flags</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This signal is used to inform the client that the + session is about to end. The client must respond by + calling + <doc:ref type="method" to="org.gnome.SessionManager.ClientPrivate.EndSessionResponse">EndSessionResponse</doc:ref> + within one second of the signal emission. + </doc:para> + <doc:para> + The flags may include: + <doc:list> + <doc:item> + <doc:term>1</doc:term> + <doc:definition>Logout is forced. + <doc:ref type="method" to="org.gnome.SessionManager.ClientPrivate.EndSessionResponse">EndSessionResponse</doc:ref> + reason and any inhibit from client will be + ignored.</doc:definition> + </doc:item> + </doc:list> + </doc:para> + <doc:para> + If the client responds with an EndSessionResponse is-ok + argument equal to FALSE and a reason then this reason may + be displayed to the user. + </doc:para> + <doc:para> + The client must not attempt to perform any actions or + interact with the user in response to this signal. Any + actions required for a clean shutdown should take place in + response to the + <doc:ref type="signal" to="org.gnome.SessionManager.ClientPrivate::EndSession">EndSession</doc:ref> signal. + </doc:para> + <doc:para> + The client should limit operations until either a + <doc:ref type="signal" to="org.gnome.SessionManager.ClientPrivate::EndSession">EndSession</doc:ref> + <doc:ref type="signal" to="org.gnome.SessionManager.ClientPrivate::CancelEndSession">CancelEndSession</doc:ref> + signal is received. + </doc:para> + </doc:description> + </doc:doc> + </signal> + + <signal name="EndSession"> + <arg name="flags" type="u"> + <doc:doc> + <doc:summary>Flags</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This signal is used to inform the client that the + session is about to end. The client must respond by + calling + <doc:ref type="method" to="org.gnome.SessionManager.ClientPrivate.EndSessionResponse">EndSessionResponse</doc:ref> + within ten seconds of the signal emission. + </doc:para> + <doc:para> + The client must not attempt to interact with the user in + response to this signal. The application will be given a + maxium of ten seconds to perform any actions required for + a clean shutdown. + </doc:para> + </doc:description> + </doc:doc> + </signal> + + <signal name="CancelEndSession"> + <doc:doc> + <doc:description> + <doc:para> + This signal indicates to the client that a previous emission of + <doc:ref type="signal" to="org.gnome.SessionManager.ClientPrivate::QueryEndSession">QueryEndSession</doc:ref> + has been cancelled. The client should resume normal operations. + </doc:para> + </doc:description> + </doc:doc> + </signal> + + </interface> +</node> diff --git a/gui/simple-greeter/org.gnome.SessionManager.xml b/gui/simple-greeter/org.gnome.SessionManager.xml new file mode 100644 index 00000000..eaf1ef53 --- /dev/null +++ b/gui/simple-greeter/org.gnome.SessionManager.xml @@ -0,0 +1,392 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> + <interface name="org.gnome.SessionManager"> + + <!-- Initialization phase interfaces --> + + <method name="Setenv"> + <arg name="variable" type="s" direction="in"> + <doc:doc> + <doc:summary>The variable name</doc:summary> + </doc:doc> + </arg> + <arg name="value" type="s" direction="in"> + <doc:doc> + <doc:summary>The value</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Adds the variable name to the application launch environment with the specified value. May only be used during the Session Manager initialization phase.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="InitializationError"> + <arg name="message" type="s" direction="in"> + <doc:doc> + <doc:summary>The error message</doc:summary> + </doc:doc> + </arg> + <arg name="fatal" type="b" direction="in"> + <doc:doc> + <doc:summary>Whether the error should be treated as fatal</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>May be used by applications launched during the Session Manager initialization phase to indicate there was a problem.</doc:para> + </doc:description> + </doc:doc> + </method> + + <!-- Running phase interfaces --> + + <method name="RegisterClient"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg type="s" name="app_id" direction="in"> + <doc:doc> + <doc:summary>The application identifier</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="client_startup_id" direction="in"> + <doc:doc> + <doc:summary>Client startup identifier</doc:summary> + </doc:doc> + </arg> + <arg type="o" name="client_id" direction="out"> + <doc:doc> + <doc:summary>The object path of the newly registered client</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Register the caller as a Session Management client.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="UnregisterClient"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg type="o" name="client_id" direction="in"> + <doc:doc> + <doc:summary>The object path of the client</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Unregister the specified client from Session Management.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="Inhibit"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg type="s" name="app_id" direction="in"> + <doc:doc> + <doc:summary>The application identifier</doc:summary> + </doc:doc> + </arg> + <arg type="u" name="toplevel_xid" direction="in"> + <doc:doc> + <doc:summary>The toplevel X window identifier</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="reason" direction="in"> + <doc:doc> + <doc:summary>The reason for the inhibit</doc:summary> + </doc:doc> + </arg> + <arg type="u" name="flags" direction="in"> + <doc:doc> + <doc:summary>Flags that spefify what should be inhibited</doc:summary> + </doc:doc> + </arg> + <arg type="u" name="inhibit_cookie" direction="out"> + <doc:doc> + <doc:summary>The cookie</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:summary> + Proactively indicates that the calling application is performing an action that should not be interrupted and sets a reason to be displayed to the user when an interruption is about to take placea. + </doc:summary> + <doc:description> + <doc:para>Applications should invoke this method when they begin an operation that + should not be interrupted, such as creating a CD or DVD. The types of actions + that may be blocked are specified by the flags parameter. When the application + completes the operation it should call <doc:ref type="method" to="org.gnome.SessionManager.Uninhibit">Uninhibit()</doc:ref> + or disconnect from the session bus. + </doc:para> + <doc:para> + Applications should not expect that they will always be able to block the + action. In most cases, users will be given the option to force the action + to take place. + </doc:para> + <doc:para> + Reasons should be short and to the point. + </doc:para> + <doc:para> + The flags parameter must include at least one of the following: + <doc:list> + <doc:item> + <doc:term>1</doc:term> + <doc:definition>Inhibit logging out</doc:definition> + </doc:item> + <doc:item> + <doc:term>2</doc:term> + <doc:definition>Inhibit user switching</doc:definition> + </doc:item> + <doc:item> + <doc:term>4</doc:term> + <doc:definition>Inhibit suspending the session or computer</doc:definition> + </doc:item> + <doc:item> + <doc:term>8</doc:term> + <doc:definition>Inhibit the session being marked as idle</doc:definition> + </doc:item> + </doc:list> + Values for flags may be bitwise or'ed together. + </doc:para> + <doc:para> + The returned cookie is used to uniquely identify this request. It should be used + as an argument to <doc:ref type="method" to="org.gnome.SessionManager.Uninhibit">Uninhibit()</doc:ref> in + order to remove the request. + </doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="Uninhibit"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg type="u" name="inhibit_cookie" direction="in"> + <doc:doc> + <doc:summary>The cookie</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Cancel a previous call to <doc:ref type="method" to="org.gnome.SessionManager.Inhibit">Inhibit()</doc:ref> identified by the cookie.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="IsInhibited"> + <arg type="u" name="flags" direction="in"> + <doc:doc> + <doc:summary>Flags that spefify what should be inhibited</doc:summary> + </doc:doc> + </arg> + <arg type="b" name="is_inhibited" direction="out"> + <doc:doc> + <doc:summary>Returns TRUE if any of the operations in the bitfield flags are inhibited</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Determine if operation(s) specified by the flags + are currently inhibited. Flags are same as those accepted + by the + <doc:ref type="method" to="org.gnome.SessionManager.Inhibit">Inhibit()</doc:ref> + method.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="GetClients"> + <arg name="clients" direction="out" type="ao"> + <doc:doc> + <doc:summary>an array of client IDs</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This gets a list of all the <doc:ref type="interface" to="org.gnome.SessionManager.Client">Clients</doc:ref> + that are currently known to the session manager.</doc:para> + <doc:para>Each Client ID is an D-Bus object path for the object that implements the + <doc:ref type="interface" to="org.gnome.SessionManager.Client">Client</doc:ref> interface.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="org.gnome.SessionManager.Client">org.gnome.SessionManager.Client</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="GetInhibitors"> + <arg name="inhibitors" direction="out" type="ao"> + <doc:doc> + <doc:summary>an array of inhibitor IDs</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This gets a list of all the <doc:ref type="interface" to="org.gnome.SessionManager.Inhibitor">Inhibitors</doc:ref> + that are currently known to the session manager.</doc:para> + <doc:para>Each Inhibitor ID is an D-Bus object path for the object that implements the + <doc:ref type="interface" to="org.gnome.SessionManager.Inhibitor">Inhibitor</doc:ref> interface.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="org.gnome.SessionManager.Inhibitor">org.gnome.SessionManager.Inhibitor</doc:ref></doc:seealso> + </doc:doc> + </method> + + + <method name="IsAutostartConditionHandled"> + <arg name="condition" direction="in" type="s"> + <doc:doc> + <doc:summary>The autostart condition string</doc:summary> + </doc:doc> + </arg> + <arg name="handled" direction="out" type="b"> + <doc:doc> + <doc:summary>True if condition is handled, false otherwise</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Allows the caller to determine whether the session manager is + handling changes to the specified autostart condition.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="Shutdown"> + <doc:doc> + <doc:description> + <doc:para>Request a shutdown dialog</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="CanShutdown"> + <arg name="is_available" direction="out" type="b"> + <doc:doc> + <doc:summary>True if shutdown is available to the user, false otherwise</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Allows the caller to determine whether or not it's okay to show + a shutdown option in the UI</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="Logout"> + <arg name="mode" type="u" direction="in"> + <doc:doc> + <doc:summary>The type of logout that is being requested</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Request a logout dialog</doc:para> + <doc:para> + Allowed values for the mode parameter are: + <doc:list> + <doc:item> + <doc:term>0</doc:term> + <doc:definition>Normal.</doc:definition> + </doc:item> + <doc:item> + <doc:term>1</doc:term> + <doc:definition>No confirmation inferface should be shown.</doc:definition> + </doc:item> + <doc:item> + <doc:term>2</doc:term> + <doc:definition>Forcefully logout. No confirmation will be shown and any inhibitors will be ignored.</doc:definition> + </doc:item> + </doc:list> + Values for flags may be bitwise or'ed together. + </doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="IsSessionRunning"> + <arg name="running" direction="out" type="b"> + <doc:doc> + <doc:summary>True if the session has entered the Running phase, false otherwise</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Allows the caller to determine whether the session manager + has entered the Running phase, in case the client missed the + SessionRunning signal.</doc:para> + </doc:description> + </doc:doc> + </method> + + <!-- Signals --> + + <signal name="ClientAdded"> + <arg name="id" type="o"> + <doc:doc> + <doc:summary>The object path for the added client</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Emitted when a client has been added to the session manager. + </doc:para> + </doc:description> + </doc:doc> + </signal> + <signal name="ClientRemoved"> + <arg name="id" type="o"> + <doc:doc> + <doc:summary>The object path for the removed client</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Emitted when a client has been removed from the session manager. + </doc:para> + </doc:description> + </doc:doc> + </signal> + + <signal name="InhibitorAdded"> + <arg name="id" type="o"> + <doc:doc> + <doc:summary>The object path for the added inhibitor</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Emitted when an inhibitor has been added to the session manager. + </doc:para> + </doc:description> + </doc:doc> + </signal> + <signal name="InhibitorRemoved"> + <arg name="id" type="o"> + <doc:doc> + <doc:summary>The object path for the removed inhibitor</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Emitted when an inhibitor has been removed from the session manager. + </doc:para> + </doc:description> + </doc:doc> + </signal> + + <signal name="SessionRunning"> + <doc:doc> + <doc:description> + <doc:para>Indicates the session has entered the Running phase.</doc:para> + </doc:description> + </doc:doc> + </signal> + + <signal name="SessionOver"> + <doc:doc> + <doc:description> + <doc:para>Indicates the session is about to end.</doc:para> + </doc:description> + </doc:doc> + </signal> + + </interface> +</node> diff --git a/gui/simple-greeter/test-a11y-preferences.c b/gui/simple-greeter/test-a11y-preferences.c new file mode 100644 index 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 <jmccann@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include <stdlib.h> +#include <libintl.h> +#include <locale.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include "gdm-a11y-preferences-dialog.h" + +int +main (int argc, char *argv[]) +{ + GtkWidget *dialog; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + setlocale (LC_ALL, ""); + + gtk_init (&argc, &argv); + + dialog = gdm_a11y_preferences_dialog_new (); + /*gtk_widget_set_size_request (dialog, 480, 128);*/ + + gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); + + return 0; +} diff --git a/gui/simple-greeter/test-filesystem-type.c b/gui/simple-greeter/test-filesystem-type.c new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include <stdlib.h> +#include <libintl.h> +#include <locale.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +#include <glib/gi18n.h> +#include <gio/gio.h> + +static char * +get_filesystem_type (const char *path) +{ + GFile *file; + GFileInfo *file_info; + GError *error; + char *filesystem_type; + + file = g_file_new_for_path (path); + error = NULL; + file_info = g_file_query_filesystem_info (file, + G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, + NULL, + &error); + if (file_info == NULL) { + g_warning ("Unable to query filesystem type: %s", error->message); + g_error_free (error); + g_object_unref (file); + return NULL; + } + + filesystem_type = g_strdup (g_file_info_get_attribute_string (file_info, + G_FILE_ATTRIBUTE_FILESYSTEM_TYPE)); + + g_object_unref (file); + g_object_unref (file_info); + + return filesystem_type; +} + +static void +print_fstype (char **paths) +{ + int i; + + i = 0; + while (paths[i] != NULL) { + char *fstype; + fstype = get_filesystem_type (paths[i]); + g_print ("%s is %s\n", paths[i], fstype); + g_free (fstype); + i++; + } +} + +int +main (int argc, char *argv[]) +{ + GOptionContext *ctx; + char **paths = NULL; + GOptionEntry options[] = { + {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &paths, NULL}, + {NULL} + }; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + setlocale (LC_ALL, ""); + + /* Option parsing */ + ctx = g_option_context_new ("- Test filesystem type"); + g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE); + g_option_context_parse (ctx, &argc, &argv, NULL); + g_option_context_free (ctx); + + if (paths != NULL) { + print_fstype (paths); + + g_strfreev (paths); + } + + return 0; +} diff --git a/gui/simple-greeter/test-greeter-background.c b/gui/simple-greeter/test-greeter-background.c new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include <stdlib.h> +#include <libintl.h> +#include <locale.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include "gdm-greeter-background.h" + +int +main (int argc, char *argv[]) +{ + GtkWidget *background; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + setlocale (LC_ALL, ""); + + gtk_init (&argc, &argv); + + background = gdm_greeter_background_new (); + gtk_widget_show_all (background); + + gtk_main (); + + return 0; +} diff --git a/gui/simple-greeter/test-greeter-login-window.c b/gui/simple-greeter/test-greeter-login-window.c new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include <stdlib.h> +#include <libintl.h> +#include <locale.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include "gdm-settings-client.h" + +#include "gdm-greeter-login-window.h" + +static guint cancel_idle_id = 0; + +static gboolean timed_login = FALSE; +static GOptionEntry entries [] = { + { "timed-login", 0, 0, G_OPTION_ARG_NONE, &timed_login, "Test timed login", NULL }, + { NULL } +}; + +static gboolean +do_cancel (GdmGreeterLoginWindow *login_window) +{ + gdm_greeter_login_window_reset (GDM_GREETER_LOGIN_WINDOW (login_window)); + cancel_idle_id = 0; + return FALSE; +} + +static void +on_select_user (GdmGreeterLoginWindow *login_window, + const char *text, + gpointer data) +{ + g_debug ("user selected: %s", text); + if (cancel_idle_id != 0) { + return; + } + cancel_idle_id = g_timeout_add_seconds (5, (GSourceFunc) do_cancel, login_window); +} + +static void +on_cancelled (GdmGreeterLoginWindow *login_window, + gpointer data) +{ + g_debug ("login cancelled"); + if (cancel_idle_id != 0) { + g_source_remove (cancel_idle_id); + cancel_idle_id = 0; + } + + gdm_greeter_login_window_reset (login_window); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *login_window; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + setlocale (LC_ALL, ""); + + gtk_init_with_args (&argc, + &argv, + "", + entries, + NULL, + NULL); + + if (! gdm_settings_client_init (DATADIR "/gdm/gdm.schemas", "/")) { + g_critical ("Unable to initialize settings client"); + exit (1); + } + + login_window = gdm_greeter_login_window_new (TRUE); + g_signal_connect (login_window, + "user-selected", + G_CALLBACK (on_select_user), + NULL); + g_signal_connect (login_window, + "cancelled", + G_CALLBACK (on_cancelled), + NULL); + if (timed_login) { + gdm_greeter_login_window_request_timed_login (GDM_GREETER_LOGIN_WINDOW (login_window), + g_get_user_name (), + 60); + } + + gtk_widget_show (login_window); + + gtk_main (); + + return 0; +} diff --git a/gui/simple-greeter/test-greeter-panel.c b/gui/simple-greeter/test-greeter-panel.c new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include <stdlib.h> +#include <libintl.h> +#include <locale.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include "gdm-greeter-panel.h" + +int +main (int argc, char *argv[]) +{ + GtkWidget *panel; + GdkDisplay *display; + GdkScreen *screen; + int monitor; + int x, y; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + setlocale (LC_ALL, ""); + + gtk_init (&argc, &argv); + + display = gdk_display_get_default (); + gdk_display_get_pointer (display, &screen, &x, &y, NULL); + monitor = gdk_screen_get_monitor_at_point (screen, x, y); + + panel = gdm_greeter_panel_new (screen, monitor, TRUE); + + gtk_widget_show (panel); + + gtk_main (); + + return 0; +} diff --git a/gui/simple-greeter/test-languages.c b/gui/simple-greeter/test-languages.c new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include <stdlib.h> +#include <libintl.h> +#include <locale.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include "gdm-languages.h" + +static void +print_languages (void) +{ + char **language_names; + int i; + + language_names = gdm_get_all_language_names (); + + for (i = 0; language_names[i] != NULL; i++) { + char *language; + char *normalized_name; + char *readable_language; + + normalized_name = gdm_normalize_language_name (language_names[i]); + language = gdm_get_language_from_name (normalized_name, normalized_name); + readable_language = gdm_get_language_from_name (normalized_name, NULL); + + g_print ("%s\t%s\t%s\t%s\n", + language_names[i], + normalized_name, + language, + readable_language); + + g_free (language); + g_free (readable_language); + g_free (normalized_name); + } + + g_strfreev (language_names); +} + +int +main (int argc, char *argv[]) +{ + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + setlocale (LC_ALL, ""); + + gtk_init (&argc, &argv); + + print_languages (); + + return 0; +} diff --git a/gui/simple-greeter/test-remote-login-window.c b/gui/simple-greeter/test-remote-login-window.c new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include <stdlib.h> +#include <libintl.h> +#include <locale.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include "gdm-remote-login-window.h" + +int +main (int argc, char *argv[]) +{ + GtkWidget *login_window; + char *std_out; + char *hostname; + GRegex *re; + GMatchInfo *match_info = NULL; + gboolean res; + GError *error; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + std_out = NULL; + g_spawn_command_line_sync (LIBEXECDIR "/gdm-host-chooser", + &std_out, + NULL, NULL, NULL); + if (std_out == NULL) { + exit (1); + } + + error = NULL; + re = g_regex_new ("hostname: (?P<hostname>[a-zA-Z0-9.-]+)", 0, 0, &error); + if (re == NULL) { + g_warning ("%s", error->message); + goto out; + } + + g_regex_match (re, std_out, 0, &match_info); + + res = g_match_info_matches (match_info); + if (! res) { + g_warning ("Unable to parse output: %s", std_out); + goto out; + } + + hostname = g_match_info_fetch_named (match_info, "hostname"); + + g_debug ("Got %s", hostname); + + setlocale (LC_ALL, ""); + + gtk_init (&argc, &argv); + + login_window = gdm_remote_login_window_new (TRUE); + g_signal_connect (login_window, "destroy", G_CALLBACK (gtk_main_quit), NULL); + gtk_widget_show (login_window); + + gdm_remote_login_window_connect (GDM_REMOTE_LOGIN_WINDOW (login_window), hostname); + + gtk_main (); + out: + + if (match_info != NULL) { + g_match_info_free (match_info); + } + if (re != NULL) { + g_regex_unref (re); + } + + g_free (std_out); + + return 0; +} diff --git a/gui/simple-greeter/test-sessions.c b/gui/simple-greeter/test-sessions.c new file mode 100644 index 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 <stdlib.h> +#include <libintl.h> +#include <locale.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include "gdm-sessions.h" + +static void +print_sessions (void) +{ + char **session_names; + int i; + + session_names = gdm_get_all_sessions (); + + for (i = 0; session_names[i] != NULL; i++) { + gboolean res; + char *name; + char *comment; + + res = gdm_get_details_for_session (session_names[i], + &name, + &comment); + if (! res) { + continue; + } + g_print ("%s\t%s\t%s\n", + session_names[i], + name, + comment); + + g_free (name); + g_free (comment); + } + + g_strfreev (session_names); +} + +int +main (int argc, char *argv[]) +{ + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + setlocale (LC_ALL, ""); + + gtk_init (&argc, &argv); + + print_sessions (); + + return 0; +} diff --git a/gui/simple-greeter/test-user-chooser.c b/gui/simple-greeter/test-user-chooser.c new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include <stdlib.h> +#include <libintl.h> +#include <locale.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include "gdm-user-chooser-dialog.h" +#include "gdm-settings-client.h" + +int +main (int argc, char *argv[]) +{ + GtkWidget *dialog; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + setlocale (LC_ALL, ""); + + gtk_init (&argc, &argv); + + if (! gdm_settings_client_init (DATADIR "/gdm/gdm.schemas", "/")) { + g_critical ("Unable to initialize settings client"); + exit (1); + } + + dialog = gdm_user_chooser_dialog_new (); + /*gtk_widget_set_size_request (dialog, 480, 128);*/ + gdm_user_chooser_dialog_set_show_user_guest (GDM_USER_CHOOSER_DIALOG (dialog), TRUE); + gdm_user_chooser_dialog_set_show_user_auto (GDM_USER_CHOOSER_DIALOG (dialog), TRUE); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) { + char *name; + + name = gdm_user_chooser_dialog_get_chosen_user_name (GDM_USER_CHOOSER_DIALOG (dialog)); + g_message ("User: %s", name ? name : "(null)"); + g_free (name); + } + gtk_widget_destroy (dialog); + + return 0; +} diff --git a/gui/simple-greeter/test-user-manager.c b/gui/simple-greeter/test-user-manager.c new file mode 100644 index 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 <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include <stdlib.h> +#include <libintl.h> +#include <locale.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include <act/act-user-manager.h> +#include <act/act-user.h> + +#include "gdm-settings-client.h" + +static ActUserManager *manager = NULL; +static GMainLoop *main_loop = NULL; + +static gboolean do_monitor = FALSE; +static gboolean fatal_warnings = FALSE; +static GOptionEntry entries [] = { + { "fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &fatal_warnings, "Make all warnings fatal", NULL }, + { "monitor", 0, 0, G_OPTION_ARG_NONE, &do_monitor, "Monitor changes", NULL }, + { NULL } +}; + +static void +on_is_loaded_changed (ActUserManager *manager, + GParamSpec *pspec, + gpointer data) +{ + GSList *users; + + g_debug ("Users loaded"); + + users = act_user_manager_list_users (manager); + while (users != NULL) { + g_print ("User: %s\n", act_user_get_user_name (users->data)); + users = g_slist_delete_link (users, users); + } + + if (! do_monitor) { + g_main_loop_quit (main_loop); + } +} + +static void +on_user_added (ActUserManager *manager, + ActUser *user, + gpointer data) +{ + g_debug ("User added: %s", act_user_get_user_name (user)); +} + +static void +on_user_removed (ActUserManager *manager, + ActUser *user, + gpointer data) +{ + g_debug ("User removed: %s", act_user_get_user_name (user)); +} + +int +main (int argc, char *argv[]) +{ + GOptionContext *context; + GError *error; + gboolean res; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + setlocale (LC_ALL, ""); + + context = g_option_context_new ("GNOME Display Manager"); + g_option_context_add_main_entries (context, entries, NULL); + g_option_context_set_ignore_unknown_options (context, TRUE); + + error = NULL; + res = g_option_context_parse (context, &argc, &argv, &error); + g_option_context_free (context); + if (! res) { + g_warning ("%s", error->message); + g_error_free (error); + return 0; + } + + if (fatal_warnings) { + GLogLevelFlags fatal_mask; + + fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); + fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL; + g_log_set_always_fatal (fatal_mask); + } + + if (! gdm_settings_client_init (DATADIR "/gdm/gdm.schemas", "/")) { + g_critical ("Unable to initialize settings client"); + exit (1); + } + + manager = act_user_manager_get_default (); + g_object_set (manager, "include-all", TRUE, NULL); + g_signal_connect (manager, + "notify::is-loaded", + G_CALLBACK (on_is_loaded_changed), + NULL); + g_signal_connect (manager, + "user-added", + G_CALLBACK (on_user_added), + NULL); + g_signal_connect (manager, + "user-removed", + G_CALLBACK (on_user_removed), + NULL); + + main_loop = g_main_loop_new (NULL, FALSE); + + g_main_loop_run (main_loop); + if (main_loop != NULL) { + g_main_loop_unref (main_loop); + } + g_object_unref (manager); + + return 0; +} |