Major rewrite of lxsession-logout
authorMarty Jack <martyj@linux.local>
Sun, 21 Feb 2010 18:27:33 +0000 (13:27 -0500)
committerMarty Jack <martyj@linux.local>
Sun, 21 Feb 2010 18:27:33 +0000 (13:27 -0500)
- Convert to using only one toplevel window to solve Compiz drawing over the controls
- Add support for shutdown/reboot via ConsoleKit
- Query HAL for capabilities rather than assuming they are present if HAL is
- Latent (untested) support for suspend/hibernate via DeviceKit/Power
- Remove GDM communication mechanism that was discontinued in GDM 2.22

configure.ac
lxsession-logout/Makefile.am
lxsession-logout/dbus-interface.c [new file with mode: 0644]
lxsession-logout/dbus-interface.h [new file with mode: 0644]
lxsession-logout/lxsession-logout.c [new file with mode: 0644]

index c38a8f5..75804c3 100644 (file)
@@ -29,28 +29,27 @@ AC_SUBST(GLIB_CFLAGS)
 AC_SUBST(GLIB_LIBS)
 
 AC_ARG_ENABLE(
-    [hal],
-    AS_HELP_STRING([--enable-hal],
-                   [Build with Linux HAL support for shutdown/reboot/suspend (default: yes)]),
-    use_hal=$enableval, use_hal="yes")
-
-if test x"$use_hal" = x"yes"; then
-dnl Check HAL support
-    hal_modules="dbus-1"
-    PKG_CHECK_MODULES(HAL, [$hal_modules], [have_hal=yes], [have_hal=no])
-
-    if test x"$have_hal" = x"yes"; then
-        AC_DEFINE([HAVE_HAL], [1], [Define to 1 if you have HAL.])
+    [dbus],
+    AS_HELP_STRING([--enable-dbus],
+                   [Build with Linux D-Bus support for shutdown/reboot/suspend (default: yes)]),
+    use_dbus=$enableval, use_dbus="yes")
+
+if test x"$use_dbus" = x"yes"; then
+dnl Check DBUS support
+    dbus_modules="dbus-1"
+    PKG_CHECK_MODULES(DBUS, [$dbus_modules], [have_dbus=yes], [have_dbus=no])
+
+    if test x"$have_dbus" = x"yes"; then
+        AC_DEFINE([HAVE_DBUS], [1], [Define to 1 if you have DBUS.])
     else
-        AC_MSG_ERROR([To use HAL support, you must have developing packages of dbus-glib-1 (>=0.31), or you can use --disable-hal to disable HAL support.])
+        AC_MSG_ERROR([To use DBUS support, you must have developing packages of dbus-glib-1 (>=0.31), or you can use --disable-dbus to disable DBUS support.])
     fi
 fi
-AM_CONDITIONAL(USE_HAL, test "$use_hal" = "yes")
-AC_SUBST(HAL_CFLAGS)
-AC_SUBST(HAL_LIBS)
+AM_CONDITIONAL(USE_DBUS, test "$use_dbus" = "yes")
+AC_SUBST(DBUS_CFLAGS)
+AC_SUBST(DBUS_LIBS)
 
 GETTEXT_PACKAGE=lxsession
-#AM_GNU_GETTEXT([external])
 AM_GNU_GETTEXT_VERSION(0.14.1)
 AC_SUBST(GETTEXT_PACKAGE)
 AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE", [Gettext package.])
@@ -85,7 +84,7 @@ echo
 echo LXSession.................................... : Version $VERSION
 echo
 echo Prefix....................................... : $prefix
-echo Linux HAL support............................ : $use_hal
+echo Linux DBUS support........................... : $use_dbus
 echo
 echo The binary will be installed in $prefix/bin
 echo
index d57adbb..a03726a 100644 (file)
@@ -1,12 +1,12 @@
 bin_PROGRAMS = lxsession-logout
 
-lxsession_logout_CFLAGS =  $(GTK_CFLAGS) $(SM_CFLAGS) $(HAL_CFLAGS) \
+lxsession_logout_CFLAGS =  $(GTK_CFLAGS) $(SM_CFLAGS) $(DBUS_CFLAGS) \
                        -DPACKAGE_LOCALE_DIR=\"$(datadir)/locale\" \
                        -DPACKAGE_DATA_DIR=\"$(datadir)\"
 
-lxsession_logout_LDADD = $(GTK_LIBS) $(SM_LIBS) $(HAL_LIBS)
+lxsession_logout_LDADD = $(GTK_LIBS) $(SM_LIBS) $(DBUS_LIBS)
 
 lxsession_logout_SOURCES= \
-       gdm-logout-action.c \
-        gdm-logout-action.h \
-       main.c
+       dbus-interface.c \
+       dbus-interface.h \
+       lxsession-logout.c
diff --git a/lxsession-logout/dbus-interface.c b/lxsession-logout/dbus-interface.c
new file mode 100644 (file)
index 0000000..86df88b
--- /dev/null
@@ -0,0 +1,371 @@
+/**
+ * Copyright (c) 2010 LxDE Developers, see the file AUTHORS for details.
+ *
+ * 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 <glib.h>
+#ifdef HAVE_DBUS
+#include <dbus/dbus.h>
+#endif
+
+/*** Mechanism independent***/
+
+#ifdef HAVE_DBUS
+/* D-Bus context. */
+static struct {
+    int connection_tried : 1;                  /* True if connection has been tried */
+    DBusConnection * connection;               /* Handle for connection */
+} dbus_context;
+
+enum { DBUS_TIMEOUT = 2000 };                  /* Reply timeout */
+
+/* Connect to the system bus.  Once a connection is made, it is saved for reuse. */
+static DBusConnection * dbus_connect(void)
+{
+    if ((dbus_context.connection == NULL) && ( ! dbus_context.connection_tried))
+    {
+        DBusError error;
+        dbus_error_init(&error);
+        dbus_context.connection = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
+        if (dbus_context.connection == NULL)
+        {
+            g_warning(G_STRLOC ": Failed to connect to the system message bus: %s", error.message);
+            dbus_error_free(&error);
+        }
+        dbus_context.connection_tried = TRUE;
+    }
+
+    return dbus_context.connection;
+}
+
+/* Send a message. */
+static DBusMessage * dbus_send_message(DBusMessage * message)
+{
+    /* Get a connection handle. */
+    DBusConnection * connection = dbus_connect();
+    if (connection == NULL)
+        return FALSE;
+
+    /* Send the message. */
+    DBusError error;
+    dbus_error_init(&error);
+    DBusMessage * reply = dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT, &error);
+    dbus_message_unref(message);
+    if (reply == NULL)
+    {
+        g_warning(G_STRLOC ": DBUS: %s", error.message);
+        dbus_error_free(&error);
+    }
+    return reply;
+}
+
+/* Read a result for a method that returns void. */
+static gboolean dbus_read_result_void(DBusMessage * reply)
+{
+    if (reply != NULL)
+        dbus_message_unref(reply);
+
+    /* No result.  Assume success. */
+    return TRUE;
+}
+
+/* Read a result for a method that returns boolean. */
+static gboolean dbus_read_result_boolean(DBusMessage * reply)
+{
+    gboolean result = FALSE;
+    if (reply != NULL)
+    {
+        /* Get the boolean result. */
+        DBusError error;
+        dbus_error_init(&error);
+        dbus_bool_t status = dbus_message_get_args(
+            reply,
+            &error,
+            DBUS_TYPE_BOOLEAN, &result,
+            DBUS_TYPE_INVALID);
+        dbus_message_unref(reply);
+        if ( ! status)
+        {
+            g_warning(G_STRLOC ": DBUS: %s", error.message);
+            dbus_error_free(&error);
+            return FALSE;
+        }
+    }
+    return result;
+}
+#endif
+
+/*** ConsoleKit mechanism ***/
+
+#ifdef HAVE_DBUS
+/* Formulate a message to the ConsoleKit Manager interface. */
+static DBusMessage * dbus_ConsoleKit_formulate_message(const char * const query)
+{
+    return dbus_message_new_method_call(
+        "org.freedesktop.ConsoleKit",
+        "/org/freedesktop/ConsoleKit/Manager",
+        "org.freedesktop.ConsoleKit.Manager",
+        query);
+}
+#endif
+
+/* Send a specified message to the ConsoleKit interface and process a boolean result. */
+static gboolean dbus_ConsoleKit_query(const char * const query)
+{
+#ifdef HAVE_DBUS
+    return dbus_read_result_boolean(dbus_send_message(dbus_ConsoleKit_formulate_message(query)));
+#else
+    return FALSE;
+#endif
+}
+
+/* Send a specified message to the ConsoleKit interface and process a void result. */
+static gboolean dbus_ConsoleKit_command(const char * const command)
+{
+#ifdef HAVE_DBUS
+    return dbus_read_result_void(dbus_send_message(dbus_ConsoleKit_formulate_message(command)));
+#else
+    return FALSE;
+#endif
+}
+
+/* Invoke the CanStop method on ConsoleKit. */
+gboolean dbus_ConsoleKit_CanStop(void)
+{
+    return dbus_ConsoleKit_query("CanStop");
+}
+
+/* Invoke the CanRestart method on ConsoleKit. */
+gboolean dbus_ConsoleKit_CanRestart(void)
+{
+    return dbus_ConsoleKit_query("CanRestart");
+}
+
+/* Invoke the Stop method on ConsoleKit. */
+gboolean dbus_ConsoleKit_Stop(void)
+{
+    return dbus_ConsoleKit_command("Stop");
+}
+
+/* Invoke the Restart method on ConsoleKit. */
+gboolean dbus_ConsoleKit_Restart(void)
+{
+    return dbus_ConsoleKit_command("Restart");
+}
+
+/*** DeviceKit Power mechanism ***/
+
+#ifdef HAVE_DBUS
+/* Formulate a message to the DeviceKit Power properties interface. */
+static DBusMessage * dbus_DeviceKit_formulate_query(const char * const query)
+{
+    return dbus_message_new_method_call(
+        "org.freedesktop.DeviceKit.Power",
+       "/org/freedesktop/DeviceKit/Power",
+       "org.freedesktop.DBus.Properties",
+        query);
+}
+
+/* Formulate a message to the DeviceKit Power interface. */
+static DBusMessage * dbus_DeviceKit_formulate_command(const char * const command)
+{
+    return dbus_message_new_method_call(
+        "org.freedesktop.DeviceKit.Power",
+       "/org/freedesktop/DeviceKit/Power",
+       "org.freedesktop.DeviceKit.Power",
+        command);
+}
+#endif
+
+/* Send a specified message to the DeviceKit interface and process a boolean result. */
+static gboolean dbus_DeviceKit_query(const char * const query)
+{
+#ifdef HAVE_DBUS
+    return dbus_read_result_boolean(dbus_send_message(dbus_DeviceKit_formulate_query(query)));
+#else
+    return FALSE;
+#endif
+}
+
+/* Send a specified message to the DeviceKit interface and process a void result. */
+static gboolean dbus_DeviceKit_command(const char * const command)
+{
+#ifdef HAVE_DBUS
+    return dbus_read_result_void(dbus_send_message(dbus_DeviceKit_formulate_command(command)));
+#else
+    return FALSE;
+#endif
+}
+
+/* Read the can-suspend property of DeviceKit/Power. */
+gboolean dbus_DeviceKit_CanSuspend(void)
+{
+    return dbus_DeviceKit_query("CanSuspend");
+}
+
+/* Read the can-hibernate property of DeviceKit/Power. */
+gboolean dbus_DeviceKit_CanHibernate(void)
+{
+    return dbus_DeviceKit_query("CanHibernate");
+}
+
+/* Invoke the Suspend method on DeviceKit/Power. */
+gboolean dbus_DeviceKit_Suspend(void)
+{
+    return dbus_DeviceKit_command("Suspend");
+}
+
+/* Invoke the Hibernate method on DeviceKit/Power. */
+gboolean dbus_DeviceKit_Hibernate(void)
+{
+    return dbus_DeviceKit_command("Hibernate");
+}
+
+/*** HAL mechanism ***/
+
+#ifdef HAVE_DBUS
+/* Formulate a message to the HAL SystemPowerManagement interface. */
+static DBusMessage * dbus_HAL_formulate_message(const char * const query)
+{
+    return dbus_message_new_method_call(
+        "org.freedesktop.Hal",
+       "/org/freedesktop/Hal/devices/computer",
+       "org.freedesktop.Hal.Device.SystemPowerManagement",
+        query);
+}
+
+/* Formulate a message to the HAL SystemPowerManagement interface to query a property. */
+static DBusMessage * dbus_HAL_formulate_property_query(const char * const method, const char * const property)
+{
+    DBusMessage * message = dbus_message_new_method_call(
+        "org.freedesktop.Hal",
+       "/org/freedesktop/Hal/devices/computer",
+       "org.freedesktop.Hal.Device",
+        method);
+    if (message != NULL)
+        dbus_message_append_args(message, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID);
+    return message;
+}
+
+static DBusMessage * dbus_HAL_formulate_boolean_property_query(const char * const property)
+{
+    return dbus_HAL_formulate_property_query("GetPropertyBoolean", property);
+}
+
+static DBusMessage * dbus_HAL_formulate_string_property_query(const char * const property)
+{
+    return dbus_HAL_formulate_property_query("GetPropertyString", property);
+}
+#endif
+
+/* Send a specified property query to the HAL interface and process whether the result exists. */
+static gboolean dbus_HAL_string_exists_query(const char * const property)
+{
+#ifdef HAVE_DBUS
+    DBusMessage * message = dbus_HAL_formulate_string_property_query(property);
+    if (message == NULL)
+        return FALSE;
+    DBusMessage * reply = dbus_send_message(message);
+    if (reply == NULL)
+       return FALSE;
+    dbus_message_unref(reply);
+    return TRUE;
+#else
+    return FALSE;
+#endif
+}
+
+/* Send a specified property query to the HAL interface and process a boolean result. */
+static gboolean dbus_HAL_boolean_query(const char * const property)
+{
+#ifdef HAVE_DBUS
+    return dbus_read_result_boolean(dbus_send_message(dbus_HAL_formulate_boolean_property_query(property)));
+#else
+    return FALSE;
+#endif
+}
+
+/* Send a specified message to the HAL interface and process a void result. */
+static gboolean dbus_HAL_command(const char * const command)
+{
+#ifdef HAVE_DBUS
+    /* Formulate the message. */
+    DBusMessage * message = dbus_HAL_formulate_message(command);
+    if (message == NULL)
+       return FALSE;
+
+    /* Suspend has an argument. */
+    if (strcmp(command, "Suspend") == 0)
+    {
+        dbus_int32_t suspend_arg = 0;
+        dbus_message_append_args(message, DBUS_TYPE_INT32, &suspend_arg, DBUS_TYPE_INVALID);
+    }
+
+    /* Send the message and wait for a reply. */
+    return dbus_read_result_void(dbus_send_message(message));
+#else
+    return FALSE;
+#endif
+}
+
+/* Read the can-shutdown property of HAL. */
+gboolean dbus_HAL_CanShutdown(void)
+{
+    return dbus_HAL_string_exists_query("power_management.type");
+}
+
+/* Read the can-reboot property of HAL. */
+gboolean dbus_HAL_CanReboot(void)
+{
+    return dbus_HAL_string_exists_query("power_management.type");
+}
+
+/* Read the can-suspend property of HAL. */
+gboolean dbus_HAL_CanSuspend(void)
+{
+    return dbus_HAL_boolean_query("power_management.can_suspend");
+}
+
+/* Read the can-hibernate property of HAL. */
+gboolean dbus_HAL_CanHibernate(void)
+{
+    return dbus_HAL_boolean_query("power_management.can_hibernate");
+}
+
+/* Invoke the Shutdown method on HAL. */
+gboolean dbus_HAL_Shutdown(void)
+{
+    return dbus_HAL_command("Shutdown");
+}
+
+/* Invoke the Reboot method on HAL. */
+gboolean dbus_HAL_Reboot(void)
+{
+    return dbus_HAL_command("Reboot");
+}
+
+/* Invoke the Suspend method on HAL. */
+gboolean dbus_HAL_Suspend(void)
+{
+    return dbus_HAL_command("Suspend");
+}
+
+/* Invoke the Hibernate method on HAL. */
+gboolean dbus_HAL_Hibernate(void)
+{
+    return dbus_HAL_command("Hibernate");
+}
diff --git a/lxsession-logout/dbus-interface.h b/lxsession-logout/dbus-interface.h
new file mode 100644 (file)
index 0000000..465d15a
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2010 LxDE Developers, see the file AUTHORS for details.
+ *
+ * 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 _DBUS_INTERFACE_H
+#define _DBUS_INTERFACE_H
+
+#include <glib.h>
+
+/* Interface to ConsoleKit for shutdown and reboot. */
+extern gboolean dbus_ConsoleKit_CanStop(void);
+extern gboolean dbus_ConsoleKit_CanRestart(void);
+extern gboolean dbus_ConsoleKit_Stop(void);
+extern gboolean dbus_ConsoleKit_Restart(void);
+
+/* Interface to DeviceKit/Power for suspend and hibernate. */
+extern gboolean dbus_DeviceKit_CanSuspend(void);
+extern gboolean dbus_DeviceKit_CanHibernate(void);
+extern gboolean dbus_DeviceKit_Suspend(void);
+extern gboolean dbus_DeviceKit_Hibernate(void);
+
+/* Interface to HAL for shutdown, reboot, suspend, and hibernate.
+ * HAL is being replaced by the above two mechanisms; this support is legacy. */
+extern gboolean dbus_HAL_CanShutdown(void);
+extern gboolean dbus_HAL_CanReboot(void);
+extern gboolean dbus_HAL_CanSuspend(void);
+extern gboolean dbus_HAL_CanHibernate(void);
+gboolean dbus_HAL_Shutdown(void);
+gboolean dbus_HAL_Reboot(void);
+gboolean dbus_HAL_Suspend(void);
+gboolean dbus_HAL_Hibernate(void);
+
+#endif
diff --git a/lxsession-logout/lxsession-logout.c b/lxsession-logout/lxsession-logout.c
new file mode 100644 (file)
index 0000000..a4aecc6
--- /dev/null
@@ -0,0 +1,479 @@
+/**
+ * Copyright (c) 2010 LxDE Developers, see the file AUTHORS for details.
+ *
+ * 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 <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <glib/gi18n.h>
+#include <sys/types.h>
+#include <signal.h>
+
+#include "dbus-interface.h"
+
+/* Command parameters. */
+static char * prompt = NULL;
+static char * banner_side = NULL;
+static char * banner_path = NULL;
+
+static GOptionEntry opt_entries[] =
+{
+    { "prompt", 'p', 0, G_OPTION_ARG_STRING, &prompt, N_("Custom message to show on the dialog"), N_("message") },
+    { "banner", 'b', 0, G_OPTION_ARG_STRING, &banner_path, N_("Banner to show on the dialog"), N_("image file") },
+    { "side", 's', 0, G_OPTION_ARG_STRING, &banner_side, N_("Position of the banner"), "top|left|right|bottom" },
+    { NULL }
+};
+
+typedef struct {
+    GPid lxsession_pid;                        /* Process ID of lxsession */
+
+    int shutdown_available : 1;                /* Shutdown is available */
+    int reboot_available : 1;          /* Reboot is available */
+    int suspend_available : 1;         /* Suspend is available */
+    int hibernate_available : 1;       /* Hibernate is available */
+    int switch_user_available : 1;     /* Switch User is available */
+
+    int shutdown_ConsoleKit : 1;       /* Shutdown is available via ConsoleKit */
+    int reboot_ConsoleKit : 1;         /* Reboot is available via ConsoleKit */
+    int suspend_DeviceKit : 1;         /* Suspend is available via DeviceKit */
+    int hibernate_DeviceKit : 1;       /* Hibernate is available via DeviceKit */
+    int shutdown_HAL : 1;              /* Shutdown is available via HAL */
+    int reboot_HAL : 1;                        /* Reboot is available via HAL */
+    int suspend_HAL : 1;               /* Suspend is available via HAL */
+    int hibernate_HAL : 1;             /* Hibernate is available via HAL */
+    int switch_user_KDE : 1;           /* Switch User is available via KDE */
+
+} HandlerContext;
+
+static void logout_clicked(GtkButton * button, HandlerContext * handler_context);
+static void shutdown_clicked(GtkButton * button, HandlerContext * handler_context);
+static void reboot_clicked(GtkButton * button, HandlerContext * handler_context);
+static void suspend_clicked(GtkButton * button, HandlerContext * handler_context);
+static void hibernate_clicked(GtkButton * button, HandlerContext * handler_context);
+static void switch_user_clicked(GtkButton * button, HandlerContext * handler_context);
+static void cancel_clicked(GtkButton * button, gpointer user_data);
+static GtkPositionType get_banner_position(void);
+static GdkPixbuf * get_background_pixbuf(void);
+
+/* Handler for "clicked" signal on Logout button. */
+static void logout_clicked(GtkButton * button, HandlerContext * handler_context)
+{
+    kill(handler_context->lxsession_pid, SIGTERM);
+    gtk_main_quit();
+}
+
+/* Handler for "clicked" signal on Shutdown button. */
+static void shutdown_clicked(GtkButton * button, HandlerContext * handler_context)
+{
+    if (handler_context->shutdown_ConsoleKit)
+        dbus_ConsoleKit_Stop();
+    else if (handler_context->shutdown_HAL)
+        dbus_HAL_Shutdown();
+    gtk_main_quit();
+}
+
+/* Handler for "clicked" signal on Reboot button. */
+static void reboot_clicked(GtkButton * button, HandlerContext * handler_context)
+{
+    if (handler_context->reboot_ConsoleKit)
+        dbus_ConsoleKit_Restart();
+    else if (handler_context->reboot_HAL)
+        dbus_HAL_Reboot();
+    gtk_main_quit();
+}
+
+/* Handler for "clicked" signal on Suspend button. */
+static void suspend_clicked(GtkButton * button, HandlerContext * handler_context)
+{
+    if (handler_context->suspend_DeviceKit)
+        dbus_DeviceKit_Suspend();
+    else if (handler_context->suspend_HAL)
+        dbus_HAL_Suspend();
+    gtk_main_quit();
+}
+
+/* Handler for "clicked" signal on Hibernate button. */
+static void hibernate_clicked(GtkButton * button, HandlerContext * handler_context)
+{
+    if (handler_context->hibernate_DeviceKit)
+        dbus_DeviceKit_Hibernate();
+    else if (handler_context->hibernate_HAL)
+        dbus_HAL_Hibernate();
+    gtk_main_quit();
+}
+
+/* Handler for "clicked" signal on Switch User button. */
+static void switch_user_clicked(GtkButton * button, HandlerContext * handler_context)
+{
+    if (handler_context->switch_user_KDE)
+        g_spawn_command_line_sync("kdmctl reserve", NULL, NULL, NULL, NULL);
+    gtk_main_quit();
+}
+
+/* Handler for "clicked" signal on Cancel button. */
+static void cancel_clicked(GtkButton * button, gpointer user_data)
+{
+    gtk_main_quit();
+}
+
+/* Convert the --side parameter to a GtkPositionType. */
+static GtkPositionType get_banner_position(void)
+{
+    if (banner_side != NULL)
+    {
+        if (strcmp(banner_side, "right") == 0)
+            return GTK_POS_RIGHT;
+        if (strcmp(banner_side, "top") == 0)
+            return GTK_POS_TOP;
+        if (strcmp(banner_side, "bottom") == 0)
+            return GTK_POS_BOTTOM;
+    }
+    return GTK_POS_LEFT;
+}
+
+/* Get the background pixbuf. */
+static GdkPixbuf * get_background_pixbuf(void)
+{
+    /* Get the root window pixmap. */
+    GdkScreen * screen = gdk_screen_get_default();
+    GdkPixbuf * pixbuf = gdk_pixbuf_get_from_drawable(
+        NULL,                                  /* Allocate a new pixbuf */
+        gdk_get_default_root_window(),         /* The drawable */
+        NULL,                                  /* Its colormap */
+        0, 0, 0, 0,                            /* Coordinates */
+        gdk_screen_get_width(screen),          /* Width */
+        gdk_screen_get_height(screen));                /* Height */
+
+    /* Make the background darker. */
+    if (pixbuf != NULL)
+    {
+        unsigned char * pixels = gdk_pixbuf_get_pixels(pixbuf);
+        int width = gdk_pixbuf_get_width(pixbuf);
+        int height = gdk_pixbuf_get_height(pixbuf);
+        int pixel_stride = ((gdk_pixbuf_get_has_alpha(pixbuf)) ? 4 : 3);
+        int row_stride = gdk_pixbuf_get_rowstride(pixbuf);
+        int y;
+        for (y = 0; y < height; y += 1)
+        {
+            unsigned char * p = pixels;
+            int x;
+            for (x = 0; x < width; x += 1)
+            {
+                p[0] = p[0] / 2;
+                p[1] = p[1] / 2;
+                p[2] = p[2] / 2;
+                p += pixel_stride;
+            }
+            pixels += row_stride;
+        }
+    }
+    return pixbuf;
+}
+
+/* Handler for "expose_event" on drawing areas. */
+gboolean expose_event(GtkWidget * widget, GdkEventExpose * event, GdkPixbuf * pixbuf)
+{
+    if (pixbuf != NULL)
+    {
+        /* Copy the appropriate rectangle of the root window pixmap to the drawing area.
+         * All drawing areas are immediate children of the toplevel window, so the allocation yields the source coordinates directly. */
+        gdk_draw_pixbuf(
+            widget->window,                                    /* Drawable to render to */
+            NULL,                                              /* GC for clipping */
+            pixbuf,                                            /* Source pixbuf */
+            widget->allocation.x, widget->allocation.y,                /* Source coordinates */
+            0, 0,                                              /* Destination coordinates */
+            widget->allocation.width, widget->allocation.height,
+            GDK_RGB_DITHER_NORMAL,                             /* Dither type */
+            0, 0);                                             /* Dither offsets */
+    }
+    return TRUE;
+}
+
+/* Main program. */
+int main(int argc, char * argv[])
+{
+#ifdef ENABLE_NLS
+    setlocale(LC_ALL, "");
+    bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
+    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+    textdomain (GETTEXT_PACKAGE);
+#endif
+
+    /* Initialize GTK (via g_option_context_parse) and parse command line arguments. */
+    GOptionContext * context = g_option_context_new("");
+    g_option_context_add_main_entries(context, opt_entries, GETTEXT_PACKAGE);
+    g_option_context_add_group(context, gtk_get_option_group(TRUE));
+    GError * err = NULL;
+    if ( ! g_option_context_parse(context, &argc, &argv, &err))
+    {
+        g_print(_("Error: %s\n"), err->message);
+        g_error_free(err);
+        return 1;
+    }
+    g_option_context_free(context);
+
+    HandlerContext handler_context;
+    memset(&handler_context, 0, sizeof(handler_context));
+
+    /* Ensure that we are running under lxsession. */
+    const char * p = g_getenv("_LXSESSION_PID");
+    if (p != NULL) handler_context.lxsession_pid = atoi(p);
+    if (handler_context.lxsession_pid == 0)
+    {
+        g_print( _("Error: %s\n"), _("LXSession is not running."));
+        return 1;
+    }   
+
+    /* Initialize capabilities of the ConsoleKit mechanism. */
+    if (dbus_ConsoleKit_CanStop())
+    {
+        handler_context.shutdown_available = TRUE;
+        handler_context.shutdown_ConsoleKit = TRUE;
+    }
+    if (dbus_ConsoleKit_CanRestart())
+    {
+        handler_context.reboot_available = TRUE;
+        handler_context.reboot_ConsoleKit = TRUE;
+    }
+
+#ifdef LATENT_DEVICEKIT_SUPPORT
+    /* Initialize capabilities of the DeviceKit mechanism. */
+    if (dbus_DeviceKit_CanSuspend())
+    {
+        handler_context.suspend_available = TRUE;
+        handler_context.suspend_DeviceKit = TRUE;
+    }
+    if (dbus_DeviceKit_CanHibernate())
+    {
+        handler_context.hibernate_available = TRUE;
+        handler_context.hibernate_DeviceKit = TRUE;
+    }
+#endif
+
+    /* Initialize capabilities of the HAL mechanism. */
+    if (dbus_HAL_CanShutdown())
+    {
+        handler_context.shutdown_available = TRUE;
+        handler_context.shutdown_HAL = TRUE;
+    }
+    if (dbus_HAL_CanReboot())
+    {
+        handler_context.reboot_available = TRUE;
+        handler_context.reboot_HAL = TRUE;
+    }
+    if (dbus_HAL_CanSuspend())
+    {
+        handler_context.suspend_available = TRUE;
+        handler_context.suspend_HAL = TRUE;
+    }
+    if (dbus_HAL_CanHibernate())
+    {
+        handler_context.hibernate_available = TRUE;
+        handler_context.hibernate_HAL = TRUE;
+    }
+
+    /* If we are under KDM, its "Switch User" is available. */
+    if (g_file_test("/var/run/kdm.pid", G_FILE_TEST_EXISTS))
+    {
+        gchar * test = g_find_program_in_path("kdmctl");
+        if (test != NULL)
+        {
+            g_free(test);
+            handler_context.switch_user_available = TRUE;
+            handler_context.switch_user_KDE = TRUE;
+        }
+    }
+
+    /* Make the button images accessible. */
+    gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), PACKAGE_DATA_DIR "/lxsession/images");
+
+    /* Get the background pixbuf. */
+    GdkPixbuf * pixbuf = get_background_pixbuf();
+
+    /* Create the toplevel window. */
+    GtkWidget * window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    gtk_window_set_decorated(GTK_WINDOW(window), FALSE);
+    gtk_window_fullscreen(GTK_WINDOW(window));
+
+    /* Create a vertical box as the child of the toplevel window. */
+    GtkWidget * outermost = gtk_vbox_new(FALSE, 0);
+    gtk_container_add(GTK_CONTAINER(window), outermost);
+
+    /* Create a drawing area as the child of the toplevel window.
+     * This drawing area is as wide as the screen and as tall as the area above the user controls. */
+    GtkWidget * top_drawing_area = gtk_drawing_area_new();
+    gtk_box_pack_start(GTK_BOX(outermost), top_drawing_area, TRUE, TRUE, 0);
+    g_signal_connect(G_OBJECT(top_drawing_area), "expose_event", G_CALLBACK(expose_event), pixbuf);
+
+    /* Create a horizontal box as the child of the outermost box. */
+    GtkWidget * horizontal = gtk_hbox_new(FALSE, 0);
+    gtk_box_pack_start(GTK_BOX(outermost), horizontal, FALSE, FALSE, 0);
+
+    /* Create a drawing area as the child of the toplevel window.
+     * This drawing area is as wide as the screen and as tall as the area below the user controls. */
+    GtkWidget * bottom_drawing_area = gtk_drawing_area_new();
+    gtk_box_pack_start(GTK_BOX(outermost), bottom_drawing_area, TRUE, TRUE, 0);
+    g_signal_connect(G_OBJECT(bottom_drawing_area), "expose_event", G_CALLBACK(expose_event), pixbuf);
+
+    /* Create a drawing area as the child of the horizontal box.
+     * This drawing area is as wide as the area left of the user controls and as tall as the user controls. */
+    GtkWidget * left_drawing_area = gtk_drawing_area_new();
+    gtk_box_pack_start(GTK_BOX(horizontal), left_drawing_area, TRUE, TRUE, 0);
+    g_signal_connect(G_OBJECT(left_drawing_area), "expose_event", G_CALLBACK(expose_event), pixbuf);
+
+    /* Create a vertical box as the child of the horizontal box.  This will contain the user controls. */
+    GtkWidget * controls = gtk_vbox_new(FALSE, 0);
+    gtk_container_set_border_width(GTK_CONTAINER(controls), 6);
+
+    /* If specified, apply a user-specified banner image. */
+    if (banner_path != NULL)
+    {
+        GtkWidget * banner_image = gtk_image_new_from_file(banner_path);
+        GtkPositionType banner_position = get_banner_position();
+
+        switch (banner_position)
+        {
+            case GTK_POS_LEFT:
+            case GTK_POS_RIGHT:
+                {
+                /* Create a horizontal box to contain the image and the controls. */
+                GtkWidget * box = gtk_hbox_new(FALSE, 2);
+                gtk_box_pack_start(GTK_BOX(horizontal), box, FALSE, FALSE, 0);
+
+                /* Pack the image and a separator. */
+                gtk_misc_set_alignment(GTK_MISC(banner_image), 0.5, 0.0);
+                if (banner_position == GTK_POS_LEFT)
+                {
+                    gtk_box_pack_start(GTK_BOX(box), banner_image, FALSE, FALSE, 2);
+                    gtk_box_pack_start(GTK_BOX(box), gtk_vseparator_new(), FALSE, FALSE, 2);
+                    gtk_box_pack_start(GTK_BOX(box), controls, FALSE, FALSE, 2);
+                }
+                else
+                {
+                    gtk_box_pack_start(GTK_BOX(box), controls, FALSE, FALSE, 2);
+                    gtk_box_pack_end(GTK_BOX(box), gtk_vseparator_new(), FALSE, FALSE, 2);
+                    gtk_box_pack_end(GTK_BOX(box), banner_image, FALSE, FALSE, 2);
+                }
+                }
+                break;
+
+            case GTK_POS_TOP:
+                gtk_box_pack_start(GTK_BOX(controls), banner_image, FALSE, FALSE, 2);
+                gtk_box_pack_start(GTK_BOX(controls), gtk_hseparator_new(), FALSE, FALSE, 2);
+                gtk_box_pack_start(GTK_BOX(horizontal), controls, FALSE, FALSE, 0);
+                break;
+
+            case GTK_POS_BOTTOM:
+                gtk_box_pack_end(GTK_BOX(controls), banner_image, FALSE, FALSE, 2);
+                gtk_box_pack_end(GTK_BOX(controls), gtk_hseparator_new(), FALSE, FALSE, 2);
+                gtk_box_pack_start(GTK_BOX(horizontal), controls, FALSE, FALSE, 0);
+                break;
+        }
+    }
+    else
+        gtk_box_pack_start(GTK_BOX(horizontal), controls, FALSE, FALSE, 0);
+
+    /* Create the label. */
+    GtkWidget * label = gtk_label_new("");
+    if (prompt == NULL)
+    {
+        const char * session_name = g_getenv("DESKTOP_SESSION");
+        if (session_name == NULL)
+            session_name = "LXDE";
+        prompt = g_strdup_printf(_("<b><big>Logout %s session?</big></b>"), session_name);
+    }
+    gtk_label_set_markup(GTK_LABEL(label), prompt);
+    gtk_box_pack_start(GTK_BOX(controls), label, FALSE, FALSE, 4);
+
+    /* Create the Logout button. */
+    GtkWidget * logout_button = gtk_button_new_with_mnemonic(_("_Logout"));
+    GtkWidget * image = gtk_image_new_from_icon_name("system-log-out", GTK_ICON_SIZE_BUTTON);
+    gtk_button_set_image(GTK_BUTTON(logout_button), image);
+    g_signal_connect(G_OBJECT(logout_button), "clicked", G_CALLBACK(logout_clicked), &handler_context);
+    gtk_box_pack_start(GTK_BOX(controls), logout_button, FALSE, FALSE, 4);
+
+    /* Create the Shutdown button. */
+    if (handler_context.shutdown_available)
+    {
+        GtkWidget * shutdown_button = gtk_button_new_with_mnemonic(_("Sh_utdown"));
+        GtkWidget * image = gtk_image_new_from_icon_name("system-shutdown", GTK_ICON_SIZE_BUTTON);
+        gtk_button_set_image(GTK_BUTTON(shutdown_button), image);
+        g_signal_connect(G_OBJECT(shutdown_button), "clicked", G_CALLBACK(shutdown_clicked), &handler_context);
+        gtk_box_pack_start(GTK_BOX(controls), shutdown_button, FALSE, FALSE, 4);
+    }
+
+    /* Create the Reboot button. */
+    if (handler_context.reboot_available)
+    {
+        GtkWidget * reboot_button = gtk_button_new_with_mnemonic(_("_Reboot"));
+        GtkWidget * image = gtk_image_new_from_icon_name("gnome-session-reboot", GTK_ICON_SIZE_BUTTON);
+        gtk_button_set_image(GTK_BUTTON(reboot_button), image);
+        g_signal_connect(G_OBJECT(reboot_button), "clicked", G_CALLBACK(reboot_clicked), &handler_context);
+        gtk_box_pack_start(GTK_BOX(controls), reboot_button, FALSE, FALSE, 4);
+    }
+
+    /* Create the Suspend button. */
+    if (handler_context.suspend_available)
+    {
+        GtkWidget * suspend_button = gtk_button_new_with_mnemonic(_("_Suspend"));
+        GtkWidget * image = gtk_image_new_from_icon_name("gnome-session-suspend", GTK_ICON_SIZE_BUTTON);
+        gtk_button_set_image(GTK_BUTTON(suspend_button), image);
+        g_signal_connect(G_OBJECT(suspend_button), "clicked", G_CALLBACK(suspend_clicked), &handler_context);
+        gtk_box_pack_start(GTK_BOX(controls), suspend_button, FALSE, FALSE, 4);
+    }
+
+    /* Create the Hibernate button. */
+    if (handler_context.hibernate_available)
+    {
+        GtkWidget * hibernate_button = gtk_button_new_with_mnemonic(_("_Hibernate"));
+        GtkWidget * image = gtk_image_new_from_icon_name("gnome-session-hibernate", GTK_ICON_SIZE_BUTTON);
+        gtk_button_set_image(GTK_BUTTON(hibernate_button), image);
+        g_signal_connect(G_OBJECT(hibernate_button), "clicked", G_CALLBACK(hibernate_clicked), &handler_context);
+        gtk_box_pack_start(GTK_BOX(controls), hibernate_button, FALSE, FALSE, 4);
+    }
+
+    /* Create the Switch User button. */
+    if (handler_context.switch_user_available)
+    {
+        GtkWidget * switch_user_button = gtk_button_new_with_mnemonic(_("S_witch User"));
+        GtkWidget * image = gtk_image_new_from_icon_name("gnome-session-switch", GTK_ICON_SIZE_BUTTON);
+        gtk_button_set_image(GTK_BUTTON(switch_user_button), image);
+        g_signal_connect(G_OBJECT(switch_user_button), "clicked", G_CALLBACK(switch_user_clicked), &handler_context);
+        gtk_box_pack_start(GTK_BOX(controls), switch_user_button, FALSE, FALSE, 4);
+    }
+
+    /* Create the Cancel button. */
+    GtkWidget * cancel_button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
+    g_signal_connect(G_OBJECT(cancel_button), "clicked", G_CALLBACK(cancel_clicked), NULL);
+    gtk_box_pack_start(GTK_BOX(controls), cancel_button, FALSE, FALSE, 4);
+
+    /* Create a drawing area as the child of the horizontal box.
+     * This drawing area is as wide as the area right of the user controls and as tall as the user controls. */
+    GtkWidget * right_drawing_area = gtk_drawing_area_new();
+    gtk_box_pack_start(GTK_BOX(horizontal), right_drawing_area, TRUE, TRUE, 0);
+    g_signal_connect(G_OBJECT(right_drawing_area), "expose_event", G_CALLBACK(expose_event), pixbuf);
+
+    /* Show everything. */
+    gtk_widget_show_all(window);
+
+    /* Run the main event loop. */
+    gtk_main();
+
+    /* Return. */
+    return 0;
+}