Add ability to perform actions after a period of idleness

This commit is contained in:
Matthias Clasen 2009-11-07 00:42:06 +00:00
parent 10e0e68ecd
commit fc01e31200
2 changed files with 629 additions and 1 deletions

View File

@ -0,0 +1,618 @@
From 62efd61f95a5985f2dff7e109944e23354b00547 Mon Sep 17 00:00:00 2001
From: William Jon McCann <jmccann@redhat.com>
Date: Thu, 1 Oct 2009 02:18:56 -0400
Subject: [PATCH] Add ability to perform actions after a period of idleness
Fixes https://bugzilla.gnome.org/show_bug.cgi?id=597030
---
configure.in | 2 +
data/gnome-session.schemas.in.in | 30 +++
gnome-session/gsm-manager.c | 394 ++++++++++++++++++++++++++++++++++++--
gnome-session/gsm-presence.c | 12 ++
gnome-session/main.c | 3 +
5 files changed, 428 insertions(+), 13 deletions(-)
diff --git a/configure.in b/configure.in
index 79466c9..4c5e133 100644
--- a/configure.in
+++ b/configure.in
@@ -42,6 +42,7 @@ LIBGNOMEUI_REQUIRED=2.2.0
GTK_REQUIRED=2.14.0
DBUS_GLIB_REQUIRED=0.76
DEVKIT_POWER_REQUIRED=008
+LIBNOTIFY_REQUIRED=0.4.3
dnl ====================================================================
dnl Dependency Checks
@@ -54,6 +55,7 @@ PKG_CHECK_MODULES(GNOME_SESSION,
gtk+-2.0 >= $GTK_REQUIRED
dbus-glib-1 >= $DBUS_GLIB_REQUIRED
devkit-power-gobject >= $DEVKIT_POWER_REQUIRED
+ libnotify >= $LIBNOTIFY_REQUIRED
)
PKG_CHECK_MODULES(SESSION_PROPERTIES,
diff --git a/data/gnome-session.schemas.in.in b/data/gnome-session.schemas.in.in
index 2cd5e2d..f87e6c3 100644
--- a/data/gnome-session.schemas.in.in
+++ b/data/gnome-session.schemas.in.in
@@ -61,6 +61,36 @@
</locale>
</schema>
<schema>
+ <key>/schemas/desktop/gnome/session/max_idle_action</key>
+ <applyto>/desktop/gnome/session/max_idle_action</applyto>
+ <owner>gnome</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>The action to take after the maximum idle time</short>
+ <long>
+ The name of the action to take when the maximum allowed
+ idle time has been reached. The Delay is specified in
+ the "max_idle_time" key. Allowed values are: logout,
+ forced-logout. An empty string disables the action.
+ </long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/gnome/session/max_idle_time</key>
+ <applyto>/desktop/gnome/session/max_idle_time</applyto>
+ <owner>gnome</owner>
+ <type>int</type>
+ <default>120</default>
+ <locale name="C">
+ <short>Amount of time a user can remain idle</short>
+ <long>
+ The number of minutes a user can remain idle before the
+ max_idle_action is automatically taken.
+ </long>
+ </locale>
+ </schema>
+ <schema>
<key>/schemas/desktop/gnome/session/default_session</key>
<applyto>/desktop/gnome/session/default_session</applyto>
<owner>gnome</owner>
diff --git a/gnome-session/gsm-manager.c b/gnome-session/gsm-manager.c
index 49fefe6..417f353 100644
--- a/gnome-session/gsm-manager.c
+++ b/gnome-session/gsm-manager.c
@@ -39,6 +39,7 @@
#include <dbus/dbus-glib-lowlevel.h>
#include <devkit-power-gobject/devicekit-power.h>
+#include <libnotify/notify.h>
#include <gtk/gtk.h> /* for logout dialog */
#include <gconf/gconf-client.h>
@@ -67,6 +68,10 @@
#define GSM_MANAGER_DBUS_PATH "/org/gnome/SessionManager"
#define GSM_MANAGER_DBUS_NAME "org.gnome.SessionManager"
+#define GS_NAME "org.gnome.ScreenSaver"
+#define GS_PATH "/org/gnome/ScreenSaver"
+#define GS_INTERFACE "org.gnome.ScreenSaver"
+
#define GSM_MANAGER_PHASE_TIMEOUT 10 /* seconds */
#define GDM_FLEXISERVER_COMMAND "gdmflexiserver"
@@ -79,6 +84,8 @@
#define KEY_DESKTOP_DIR "/desktop/gnome/session"
#define KEY_IDLE_DELAY KEY_DESKTOP_DIR "/idle_delay"
+#define KEY_MAX_IDLE_TIME KEY_DESKTOP_DIR "/max_idle_time"
+#define KEY_MAX_IDLE_ACTION KEY_DESKTOP_DIR "/max_idle_action"
#define KEY_GNOME_SESSION_DIR "/apps/gnome-session/options"
#define KEY_AUTOSAVE KEY_GNOME_SESSION_DIR "/auto_save_session"
@@ -114,6 +121,11 @@ struct GsmManagerPrivate
gboolean forceful_logout;
GSList *query_clients;
guint query_timeout_id;
+ guint max_idle_timeout_id;
+ guint max_idle_warning_timeout_id;
+ guint max_idle_time_secs;
+ int max_idle_action;
+ NotifyNotification *max_idle_notification;
/* This is used for GSM_MANAGER_PHASE_END_SESSION only at the moment,
* since it uses a sublist of all running client that replied in a
* specific way */
@@ -157,6 +169,19 @@ enum {
static guint signals [LAST_SIGNAL] = { 0 };
+typedef enum {
+ ACTION_NONE = 0,
+ ACTION_LOGOUT,
+ ACTION_FORCED_LOGOUT,
+} Action;
+
+static GConfEnumStringPair actions_enum_map [] = {
+ { ACTION_NONE, "" },
+ { ACTION_LOGOUT, "logout" },
+ { ACTION_FORCED_LOGOUT, "forced-logout" },
+ { 0, NULL }
+};
+
static void gsm_manager_class_init (GsmManagerClass *klass);
static void gsm_manager_init (GsmManager *manager);
static void gsm_manager_finalize (GObject *object);
@@ -2147,6 +2172,19 @@ gsm_manager_dispose (GObject *object)
g_debug ("GsmManager: disposing manager");
+ if (manager->priv->max_idle_notification != NULL) {
+ g_object_unref (manager->priv->max_idle_notification);
+ manager->priv->max_idle_notification = NULL;
+ }
+ if (manager->priv->max_idle_warning_timeout_id > 0) {
+ g_source_remove (manager->priv->max_idle_warning_timeout_id);
+ manager->priv->max_idle_warning_timeout_id = 0;
+ }
+ if (manager->priv->max_idle_timeout_id > 0) {
+ g_source_remove (manager->priv->max_idle_timeout_id);
+ manager->priv->max_idle_timeout_id = 0;
+ }
+
if (manager->priv->clients != NULL) {
g_signal_handlers_disconnect_by_func (manager->priv->clients,
on_store_client_added,
@@ -2343,6 +2381,321 @@ load_idle_delay_from_gconf (GsmManager *manager)
}
static void
+load_max_idle_from_gconf (GsmManager *manager)
+{
+ GError *error;
+ glong value;
+ const char *str;
+
+ error = NULL;
+ value = gconf_client_get_int (manager->priv->gconf_client,
+ KEY_MAX_IDLE_TIME,
+ &error);
+ if (error == NULL) {
+ manager->priv->max_idle_time_secs = 60 * value;
+ } else {
+ g_warning ("Error retrieving configuration key '%s': %s",
+ KEY_MAX_IDLE_TIME,
+ error->message);
+ g_error_free (error);
+ }
+
+ error = NULL;
+ str = gconf_client_get_string (manager->priv->gconf_client,
+ KEY_MAX_IDLE_ACTION,
+ &error);
+ if (error == NULL) {
+ int action;
+
+ if (gconf_string_to_enum (actions_enum_map, str, &action)) {
+ manager->priv->max_idle_action = action;
+ } else {
+ manager->priv->max_idle_action = ACTION_NONE;
+ }
+ } else {
+ g_warning ("Error retrieving configuration key '%s': %s",
+ KEY_MAX_IDLE_TIME,
+ error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+request_logout (GsmManager *manager,
+ gboolean forceful_logout)
+{
+ g_debug ("GsmManager: requesting logout");
+
+ manager->priv->forceful_logout = forceful_logout;
+ manager->priv->logout_type = GSM_MANAGER_LOGOUT_LOGOUT;
+
+ end_phase (manager);
+}
+
+static gboolean
+_on_max_idle_time_timeout (GsmManager *manager)
+{
+ manager->priv->max_idle_timeout_id = 0;
+ g_debug ("GsmManager: max idle time reached");
+
+ if (manager->priv->max_idle_warning_timeout_id > 0) {
+ g_source_remove (manager->priv->max_idle_warning_timeout_id);
+ manager->priv->max_idle_warning_timeout_id = 0;
+ }
+
+ switch (manager->priv->max_idle_action) {
+ case ACTION_LOGOUT:
+ g_debug ("GsmManager: max idle action: logout");
+ /* begin non-forceful logout */
+ request_logout (manager, FALSE);
+ break;
+ case ACTION_FORCED_LOGOUT:
+ g_debug ("GsmManager: max idle action: force-logout");
+ /* begin forceful logout */
+ request_logout (manager, TRUE);
+ break;
+ case ACTION_NONE:
+ g_debug ("GsmManager: no max idle action specified");
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return FALSE;
+}
+
+static void
+on_max_idle_notification_closed (NotifyNotification *notification,
+ GsmManager *manager)
+{
+ if (manager->priv->max_idle_notification != NULL) {
+ g_object_unref (manager->priv->max_idle_notification);
+ manager->priv->max_idle_notification = NULL;
+ }
+}
+
+/* Adapted from totem_time_to_string_text */
+static char *
+time_to_string_text (long time)
+{
+ char *secs, *mins, *hours, *string;
+ int sec, min, hour;
+
+ sec = time % 60;
+ time = time - sec;
+ min = (time % (60 * 60)) / 60;
+ time = time - (min * 60);
+ hour = time / (60 * 60);
+
+ hours = g_strdup_printf (ngettext ("%d hour", "%d hours", hour), hour);
+
+ mins = g_strdup_printf (ngettext ("%d minute",
+ "%d minutes", min), min);
+
+ secs = g_strdup_printf (ngettext ("%d second",
+ "%d seconds", sec), sec);
+
+ if (hour > 0) {
+ /* hour:minutes:seconds */
+ string = g_strdup_printf (_("%s %s %s"), hours, mins, secs);
+ } else if (min > 0) {
+ /* minutes:seconds */
+ string = g_strdup_printf (_("%s %s"), mins, secs);
+ } else if (sec > 0) {
+ /* seconds */
+ string = g_strdup_printf (_("%s"), secs);
+ } else {
+ /* 0 seconds */
+ string = g_strdup (_("0 seconds"));
+ }
+
+ g_free (hours);
+ g_free (mins);
+ g_free (secs);
+
+ return string;
+}
+
+static void
+update_max_idle_notification (GsmManager *manager,
+ long secs_remaining)
+{
+ char *summary;
+ char *body;
+ char *remaining;
+ gboolean screensaver_active;
+
+ g_object_get (manager->priv->presence, "screensaver-active", &screensaver_active, NULL);
+
+ remaining = time_to_string_text (secs_remaining);
+ summary = NULL;
+ body = NULL;
+
+ switch (manager->priv->max_idle_action) {
+ case ACTION_LOGOUT:
+ case ACTION_FORCED_LOGOUT:
+ summary = g_strdup_printf (_("Automatic logout in %s"), remaining);
+ body = g_strdup (_("This session is configured to automatically log out after a period of inactivity."));
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ g_free (remaining);
+
+ if (screensaver_active) {
+ DBusGProxy *proxy;
+ GError *error;
+
+ proxy = dbus_g_proxy_new_for_name (manager->priv->connection,
+ GS_NAME,
+ GS_PATH,
+ GS_INTERFACE);
+ error = NULL;
+ if (! dbus_g_proxy_call (proxy, "ShowMessage", &error,
+ G_TYPE_STRING, summary,
+ G_TYPE_STRING, body,
+ G_TYPE_STRING, "",
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_debug ("ShowMessage failed: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (proxy);
+ } else {
+ if (manager->priv->max_idle_notification != NULL) {
+ notify_notification_update (manager->priv->max_idle_notification,
+ summary,
+ body,
+ NULL);
+ } else {
+ manager->priv->max_idle_notification
+ = notify_notification_new (summary, body, NULL, NULL);
+ notify_notification_set_timeout (manager->priv->max_idle_notification,
+ NOTIFY_EXPIRES_NEVER);
+
+ g_signal_connect (manager->priv->max_idle_notification,
+ "closed",
+ G_CALLBACK (on_max_idle_notification_closed),
+ manager);
+ }
+
+ notify_notification_show (manager->priv->max_idle_notification, NULL);
+ }
+
+ g_free (body);
+ g_free (summary);
+}
+
+static gboolean
+_on_max_idle_warning_2_timeout (GsmManager *manager)
+{
+ manager->priv->max_idle_warning_timeout_id = 0;
+
+ g_debug ("Note: will perform idle action in %f seconds",
+ 0.02 * manager->priv->max_idle_time_secs);
+ update_max_idle_notification (manager, 0.02 * manager->priv->max_idle_time_secs);
+
+ return FALSE;
+}
+
+static gboolean
+_on_max_idle_warning_5_timeout (GsmManager *manager)
+{
+ long warn_secs;
+
+ warn_secs = 0.03 * manager->priv->max_idle_time_secs;
+ manager->priv->max_idle_warning_timeout_id
+ = g_timeout_add_seconds (warn_secs,
+ (GSourceFunc)_on_max_idle_warning_2_timeout,
+ manager);
+ g_debug ("Note: will perform idle action in %f seconds",
+ 0.05 * manager->priv->max_idle_time_secs);
+ update_max_idle_notification (manager, 0.05 * manager->priv->max_idle_time_secs);
+
+ return FALSE;
+}
+
+static gboolean
+_on_max_idle_warning_10_timeout (GsmManager *manager)
+{
+ long warn_secs;
+
+ warn_secs = 0.05 * manager->priv->max_idle_time_secs;
+ manager->priv->max_idle_warning_timeout_id
+ = g_timeout_add_seconds (warn_secs,
+ (GSourceFunc)_on_max_idle_warning_5_timeout,
+ manager);
+ g_debug ("Note: will perform idle action in %f seconds",
+ 0.1 * manager->priv->max_idle_time_secs);
+ update_max_idle_notification (manager, 0.1 * manager->priv->max_idle_time_secs);
+
+ return FALSE;
+}
+
+static gboolean
+_on_max_idle_warning_20_timeout (GsmManager *manager)
+{
+ long warn_secs;
+
+ warn_secs = 0.1 * manager->priv->max_idle_time_secs;
+ manager->priv->max_idle_warning_timeout_id
+ = g_timeout_add_seconds (warn_secs,
+ (GSourceFunc)_on_max_idle_warning_10_timeout,
+ manager);
+ g_debug ("Note: will perform idle action in %f seconds",
+ 0.2 * manager->priv->max_idle_time_secs);
+
+ update_max_idle_notification (manager, 0.2 * manager->priv->max_idle_time_secs);
+
+ return FALSE;
+}
+
+static void
+reset_max_idle_timer (GsmManager *manager)
+{
+ int status;
+
+ g_object_get (manager->priv->presence, "status", &status, NULL);
+
+
+ g_debug ("Resetting max idle timer status=%d action=%d time=%ds",
+ status, manager->priv->max_idle_action, manager->priv->max_idle_time_secs);
+ if (manager->priv->max_idle_timeout_id > 0) {
+ g_source_remove (manager->priv->max_idle_timeout_id);
+ manager->priv->max_idle_timeout_id = 0;
+ }
+ if (manager->priv->max_idle_warning_timeout_id > 0) {
+ g_source_remove (manager->priv->max_idle_warning_timeout_id);
+ manager->priv->max_idle_warning_timeout_id = 0;
+ }
+
+ if (status == GSM_PRESENCE_STATUS_IDLE
+ && manager->priv->max_idle_action != ACTION_NONE) {
+ long warn_secs;
+
+ /* start counting now. probably isn't quite
+ right if we're handling a configuration
+ value change but it may not matter */
+
+ manager->priv->max_idle_timeout_id
+ = g_timeout_add_seconds (manager->priv->max_idle_time_secs,
+ (GSourceFunc)_on_max_idle_time_timeout,
+ manager);
+
+ /* start warning at 80% of the way through the idle */
+ warn_secs = 0.8 * manager->priv->max_idle_time_secs;
+ manager->priv->max_idle_warning_timeout_id
+ = g_timeout_add_seconds (warn_secs,
+ (GSourceFunc)_on_max_idle_warning_20_timeout,
+ manager);
+ }
+}
+
+static void
on_gconf_key_changed (GConfClient *client,
guint cnxn_id,
GConfEntry *entry,
@@ -2370,6 +2723,32 @@ on_gconf_key_changed (GConfClient *client,
} else {
invalid_type_warning (key);
}
+ } else if (strcmp (key, KEY_MAX_IDLE_TIME) == 0) {
+ if (value->type == GCONF_VALUE_INT) {
+ int t;
+
+ t = gconf_value_get_int (value);
+
+ manager->priv->max_idle_time_secs = t * 60;
+ reset_max_idle_timer (manager);
+ } else {
+ invalid_type_warning (key);
+ }
+ } else if (strcmp (key, KEY_MAX_IDLE_ACTION) == 0) {
+ if (value->type == GCONF_VALUE_STRING) {
+ int action;
+ const char *str;
+
+ str = gconf_value_get_string (value);
+ if (gconf_string_to_enum (actions_enum_map, str, &action)) {
+ manager->priv->max_idle_action = action;
+ } else {
+ manager->priv->max_idle_action = ACTION_NONE;
+ }
+ reset_max_idle_timer (manager);
+ } else {
+ invalid_type_warning (key);
+ }
} else if (strcmp (key, KEY_LOCK_DISABLE) == 0) {
if (value->type == GCONF_VALUE_BOOL) {
gboolean disabled;
@@ -2407,6 +2786,7 @@ on_presence_status_changed (GsmPresence *presence,
consolekit = gsm_get_consolekit ();
gsm_consolekit_set_session_idle (consolekit,
(status == GSM_PRESENCE_STATUS_IDLE));
+ reset_max_idle_timer (manager);
}
static void
@@ -2457,6 +2837,7 @@ gsm_manager_init (GsmManager *manager)
NULL, NULL);
load_idle_delay_from_gconf (manager);
+ load_max_idle_from_gconf (manager);
}
static void
@@ -2763,19 +3144,6 @@ request_hibernate (GsmManager *manager)
gtk_widget_show (manager->priv->inhibit_dialog);
}
-
-static void
-request_logout (GsmManager *manager,
- gboolean forceful_logout)
-{
- g_debug ("GsmManager: requesting logout");
-
- manager->priv->forceful_logout = forceful_logout;
- manager->priv->logout_type = GSM_MANAGER_LOGOUT_LOGOUT;
-
- end_phase (manager);
-}
-
static void
request_switch_user (GsmManager *manager)
{
diff --git a/gnome-session/gsm-presence.c b/gnome-session/gsm-presence.c
index c3918de..40f123f 100644
--- a/gnome-session/gsm-presence.c
+++ b/gnome-session/gsm-presence.c
@@ -66,6 +66,7 @@ enum {
PROP_STATUS_TEXT,
PROP_IDLE_ENABLED,
PROP_IDLE_TIMEOUT,
+ PROP_SCREENSAVER_ACTIVE,
};
@@ -187,6 +188,7 @@ on_screensaver_active_changed (DBusGProxy *proxy,
presence->priv->screensaver_active = is_active;
reset_idle_watch (presence);
set_session_idle (presence, is_active);
+ g_object_notify (G_OBJECT (presence), "screensaver-active");
}
}
@@ -439,6 +441,9 @@ gsm_presence_get_property (GObject *object,
case PROP_IDLE_TIMEOUT:
g_value_set_uint (value, self->priv->idle_timeout);
break;
+ case PROP_SCREENSAVER_ACTIVE:
+ g_value_set_boolean (value, self->priv->screensaver_active);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -532,6 +537,13 @@ gsm_presence_class_init (GsmPresenceClass *klass)
G_MAXINT,
300000,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_SCREENSAVER_ACTIVE,
+ g_param_spec_boolean ("screensaver-active",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READABLE));
dbus_g_object_type_install_info (GSM_TYPE_PRESENCE, &dbus_glib_gsm_presence_object_info);
dbus_g_error_domain_register (GSM_PRESENCE_ERROR, NULL, GSM_PRESENCE_TYPE_ERROR);
diff --git a/gnome-session/main.c b/gnome-session/main.c
index 87d46ae..90e0dce 100644
--- a/gnome-session/main.c
+++ b/gnome-session/main.c
@@ -36,6 +36,7 @@
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-bindings.h>
#include <dbus/dbus-glib-lowlevel.h>
+#include <libnotify/notify.h>
#include "gdm-signal-handler.h"
#include "gdm-log.h"
@@ -467,6 +468,8 @@ main (int argc, char **argv)
exit (1);
}
+ notify_init ("GNOME session manager");
+
gdm_log_init ();
gdm_log_set_debug (debug);
--
1.6.5.rc2

View File

@ -10,7 +10,7 @@
Summary: GNOME session manager Summary: GNOME session manager
Name: gnome-session Name: gnome-session
Version: 2.28.0 Version: 2.28.0
Release: 2%{?dist} Release: 3%{?dist}
URL: http://www.gnome.org URL: http://www.gnome.org
Source0: http://download.gnome.org/sources/gnome-session/2.28/%{name}-%{version}.tar.bz2 Source0: http://download.gnome.org/sources/gnome-session/2.28/%{name}-%{version}.tar.bz2
Source2: gnome.desktop Source2: gnome.desktop
@ -63,6 +63,9 @@ BuildRequires: xmlto
BuildRequires: DeviceKit-power-devel BuildRequires: DeviceKit-power-devel
BuildRequires: gnome-common BuildRequires: gnome-common
# for patch3
BuildRequires: libnotify-devel
Requires(pre): GConf2 >= %{gconf2_version} Requires(pre): GConf2 >= %{gconf2_version}
Requires(post): GConf2 >= %{gconf2_version} Requires(post): GConf2 >= %{gconf2_version}
Requires(preun): GConf2 >= %{gconf2_version} Requires(preun): GConf2 >= %{gconf2_version}
@ -74,6 +77,9 @@ Requires(preun): GConf2 >= %{gconf2_version}
# https://bugzilla.gnome.org/show_bug.cgi?id=598211 # https://bugzilla.gnome.org/show_bug.cgi?id=598211
Patch2: xsmp-stop.patch Patch2: xsmp-stop.patch
# https://bugzilla.gnome.org/show_bug.cgi?id=597030
Patch3: 0001-Add-ability-to-perform-actions-after-a-period-of-idl.patch
%description %description
gnome-session manages a GNOME desktop or GDM login session. It starts up gnome-session manages a GNOME desktop or GDM login session. It starts up
the other core GNOME components and handles logout and saving the session. the other core GNOME components and handles logout and saving the session.
@ -91,6 +97,7 @@ Desktop file to add GNOME to display manager session menu.
#%patch0 -p1 -b .unresponsive-timeout #%patch0 -p1 -b .unresponsive-timeout
#%patch1 -p1 -b .show-lock #%patch1 -p1 -b .show-lock
%patch2 -p1 -b .xsmp-stop %patch2 -p1 -b .xsmp-stop
%patch3 -p1 -b .max-idle
echo "ACLOCAL_AMFLAGS = -I m4" >> Makefile.am echo "ACLOCAL_AMFLAGS = -I m4" >> Makefile.am
@ -178,6 +185,9 @@ fi
%changelog %changelog
* Fri Nov 6 2009 Matthias Clasen <mclasen@redhat.com> - 2.28.0-3
- Add ability to perform actions after a period of idleness
* Fri Oct 23 2009 Matthias Clasen <mclasen@redhat.com> - 2.28.0-2 * Fri Oct 23 2009 Matthias Clasen <mclasen@redhat.com> - 2.28.0-2
- Avoid a crash on certain xsmp error conditions - Avoid a crash on certain xsmp error conditions