summaryrefslogtreecommitdiff
path: root/mate-session/gs-idle-monitor.c
diff options
context:
space:
mode:
Diffstat (limited to 'mate-session/gs-idle-monitor.c')
-rw-r--r--mate-session/gs-idle-monitor.c507
1 files changed, 507 insertions, 0 deletions
diff --git a/mate-session/gs-idle-monitor.c b/mate-session/gs-idle-monitor.c
new file mode 100644
index 0000000..05dbb02
--- /dev/null
+++ b/mate-session/gs-idle-monitor.c
@@ -0,0 +1,507 @@
+/* -*- 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors: William Jon McCann <[email protected]>
+ *
+ */
+
+#include "config.h"
+
+#include <time.h>
+#include <string.h>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/sync.h>
+
+#ifdef HAVE_XTEST
+#include <X11/keysym.h>
+#include <X11/extensions/XTest.h>
+#endif /* HAVE_XTEST */
+
+#include <glib.h>
+#include <gdk/gdkx.h>
+#include <gdk/gdk.h>
+
+#include "gs-idle-monitor.h"
+
+static void gs_idle_monitor_class_init (GSIdleMonitorClass *klass);
+static void gs_idle_monitor_init (GSIdleMonitor *idle_monitor);
+static void gs_idle_monitor_finalize (GObject *object);
+
+#define GS_IDLE_MONITOR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GS_TYPE_IDLE_MONITOR, GSIdleMonitorPrivate))
+
+struct GSIdleMonitorPrivate
+{
+ GHashTable *watches;
+ int sync_event_base;
+ XSyncCounter counter;
+
+ /* For use with XTest */
+ int *keycode;
+ int keycode1;
+ int keycode2;
+ gboolean have_xtest;
+};
+
+typedef struct
+{
+ guint id;
+ XSyncValue interval;
+ GSIdleMonitorWatchFunc callback;
+ gpointer user_data;
+ XSyncAlarm xalarm_positive;
+ XSyncAlarm xalarm_negative;
+} GSIdleMonitorWatch;
+
+static guint32 watch_serial = 1;
+
+G_DEFINE_TYPE (GSIdleMonitor, gs_idle_monitor, G_TYPE_OBJECT)
+
+static gint64
+_xsyncvalue_to_int64 (XSyncValue value)
+{
+ return ((guint64) XSyncValueHigh32 (value)) << 32
+ | (guint64) XSyncValueLow32 (value);
+}
+
+static XSyncValue
+_int64_to_xsyncvalue (gint64 value)
+{
+ XSyncValue ret;
+
+ XSyncIntsToValue (&ret, value, ((guint64)value) >> 32);
+
+ return ret;
+}
+
+static void
+gs_idle_monitor_dispose (GObject *object)
+{
+ GSIdleMonitor *monitor;
+
+ g_return_if_fail (GS_IS_IDLE_MONITOR (object));
+
+ monitor = GS_IDLE_MONITOR (object);
+
+ if (monitor->priv->watches != NULL) {
+ g_hash_table_destroy (monitor->priv->watches);
+ monitor->priv->watches = NULL;
+ }
+
+ G_OBJECT_CLASS (gs_idle_monitor_parent_class)->dispose (object);
+}
+
+static gboolean
+_find_alarm (gpointer key,
+ GSIdleMonitorWatch *watch,
+ XSyncAlarm *alarm)
+{
+ g_debug ("Searching for %d in %d,%d", (int)*alarm, (int)watch->xalarm_positive, (int)watch->xalarm_negative);
+ if (watch->xalarm_positive == *alarm
+ || watch->xalarm_negative == *alarm) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static GSIdleMonitorWatch *
+find_watch_for_alarm (GSIdleMonitor *monitor,
+ XSyncAlarm alarm)
+{
+ GSIdleMonitorWatch *watch;
+
+ watch = g_hash_table_find (monitor->priv->watches,
+ (GHRFunc)_find_alarm,
+ &alarm);
+ return watch;
+}
+
+#ifdef HAVE_XTEST
+static gboolean
+send_fake_event (GSIdleMonitor *monitor)
+{
+ if (! monitor->priv->have_xtest) {
+ return FALSE;
+ }
+
+ g_debug ("GSIdleMonitor: sending fake key");
+
+ XLockDisplay (GDK_DISPLAY());
+ XTestFakeKeyEvent (GDK_DISPLAY(),
+ *monitor->priv->keycode,
+ True,
+ CurrentTime);
+ XTestFakeKeyEvent (GDK_DISPLAY(),
+ *monitor->priv->keycode,
+ False,
+ CurrentTime);
+ XUnlockDisplay (GDK_DISPLAY());
+
+ /* Swap the keycode */
+ if (monitor->priv->keycode == &monitor->priv->keycode1) {
+ monitor->priv->keycode = &monitor->priv->keycode2;
+ } else {
+ monitor->priv->keycode = &monitor->priv->keycode1;
+ }
+
+ return TRUE;
+}
+#endif /* HAVE_XTEST */
+
+void
+gs_idle_monitor_reset (GSIdleMonitor *monitor)
+{
+ g_return_if_fail (GS_IS_IDLE_MONITOR (monitor));
+
+#ifdef HAVE_XTEST
+ /* FIXME: is there a better way to reset the IDLETIME? */
+ send_fake_event (monitor);
+#endif
+}
+
+static void
+handle_alarm_notify_event (GSIdleMonitor *monitor,
+ XSyncAlarmNotifyEvent *alarm_event)
+{
+ GSIdleMonitorWatch *watch;
+ gboolean res;
+ gboolean condition;
+
+ if (alarm_event->state == XSyncAlarmDestroyed) {
+ return;
+ }
+
+ watch = find_watch_for_alarm (monitor, alarm_event->alarm);
+
+ if (watch == NULL) {
+ g_debug ("Unable to find watch for alarm %d", (int)alarm_event->alarm);
+ return;
+ }
+
+ g_debug ("Watch %d fired, idle time = %lld",
+ watch->id,
+ _xsyncvalue_to_int64 (alarm_event->counter_value));
+
+ if (alarm_event->alarm == watch->xalarm_positive) {
+ condition = TRUE;
+ } else {
+ condition = FALSE;
+ }
+
+ res = TRUE;
+ if (watch->callback != NULL) {
+ res = watch->callback (monitor,
+ watch->id,
+ condition,
+ watch->user_data);
+ }
+
+ if (! res) {
+ /* reset all timers */
+ g_debug ("GSIdleMonitor: callback returned FALSE; resetting idle time");
+ gs_idle_monitor_reset (monitor);
+ }
+}
+
+static GdkFilterReturn
+xevent_filter (GdkXEvent *xevent,
+ GdkEvent *event,
+ GSIdleMonitor *monitor)
+{
+ XEvent *ev;
+ XSyncAlarmNotifyEvent *alarm_event;
+
+ ev = xevent;
+ if (ev->xany.type != monitor->priv->sync_event_base + XSyncAlarmNotify) {
+ return GDK_FILTER_CONTINUE;
+ }
+
+ alarm_event = xevent;
+
+ handle_alarm_notify_event (monitor, alarm_event);
+
+ return GDK_FILTER_CONTINUE;
+}
+
+static gboolean
+init_xsync (GSIdleMonitor *monitor)
+{
+ int sync_error_base;
+ int res;
+ int major;
+ int minor;
+ int i;
+ int ncounters;
+ XSyncSystemCounter *counters;
+
+ res = XSyncQueryExtension (GDK_DISPLAY (),
+ &monitor->priv->sync_event_base,
+ &sync_error_base);
+ if (! res) {
+ g_warning ("GSIdleMonitor: Sync extension not present");
+ return FALSE;
+ }
+
+ res = XSyncInitialize (GDK_DISPLAY (), &major, &minor);
+ if (! res) {
+ g_warning ("GSIdleMonitor: Unable to initialize Sync extension");
+ return FALSE;
+ }
+
+ counters = XSyncListSystemCounters (GDK_DISPLAY (), &ncounters);
+ for (i = 0; i < ncounters; i++) {
+ if (counters[i].name != NULL
+ && strcmp (counters[i].name, "IDLETIME") == 0) {
+ monitor->priv->counter = counters[i].counter;
+ break;
+ }
+ }
+ XSyncFreeSystemCounterList (counters);
+
+ if (monitor->priv->counter == None) {
+ g_warning ("GSIdleMonitor: IDLETIME counter not found");
+ return FALSE;
+ }
+
+ gdk_window_add_filter (NULL, (GdkFilterFunc)xevent_filter, monitor);
+
+ return TRUE;
+}
+
+static void
+_init_xtest (GSIdleMonitor *monitor)
+{
+#ifdef HAVE_XTEST
+ int a, b, c, d;
+
+ XLockDisplay (GDK_DISPLAY());
+ monitor->priv->have_xtest = (XTestQueryExtension (GDK_DISPLAY(), &a, &b, &c, &d) == True);
+ if (monitor->priv->have_xtest) {
+ monitor->priv->keycode1 = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_L);
+ if (monitor->priv->keycode1 == 0) {
+ g_warning ("keycode1 not existant");
+ }
+ monitor->priv->keycode2 = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_R);
+ if (monitor->priv->keycode2 == 0) {
+ monitor->priv->keycode2 = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_L);
+ if (monitor->priv->keycode2 == 0) {
+ g_warning ("keycode2 not existant");
+ }
+ }
+ monitor->priv->keycode = &monitor->priv->keycode1;
+ }
+ XUnlockDisplay (GDK_DISPLAY());
+#endif /* HAVE_XTEST */
+}
+
+static GObject *
+gs_idle_monitor_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GSIdleMonitor *monitor;
+
+ monitor = GS_IDLE_MONITOR (G_OBJECT_CLASS (gs_idle_monitor_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_properties));
+
+ _init_xtest (monitor);
+
+ if (! init_xsync (monitor)) {
+ g_object_unref (monitor);
+ return NULL;
+ }
+
+ return G_OBJECT (monitor);
+}
+
+static void
+gs_idle_monitor_class_init (GSIdleMonitorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gs_idle_monitor_finalize;
+ object_class->dispose = gs_idle_monitor_dispose;
+ object_class->constructor = gs_idle_monitor_constructor;
+
+ g_type_class_add_private (klass, sizeof (GSIdleMonitorPrivate));
+}
+
+static guint32
+get_next_watch_serial (void)
+{
+ guint32 serial;
+
+ serial = watch_serial++;
+
+ if ((gint32)watch_serial < 0) {
+ watch_serial = 1;
+ }
+
+ /* FIXME: make sure it isn't in the hash */
+
+ return serial;
+}
+
+static GSIdleMonitorWatch *
+idle_monitor_watch_new (guint interval)
+{
+ GSIdleMonitorWatch *watch;
+
+ watch = g_slice_new0 (GSIdleMonitorWatch);
+ watch->interval = _int64_to_xsyncvalue ((gint64)interval);
+ watch->id = get_next_watch_serial ();
+ watch->xalarm_positive = None;
+ watch->xalarm_negative = None;
+
+ return watch;
+}
+
+static void
+idle_monitor_watch_free (GSIdleMonitorWatch *watch)
+{
+ if (watch == NULL) {
+ return;
+ }
+ if (watch->xalarm_positive != None) {
+ XSyncDestroyAlarm (GDK_DISPLAY (), watch->xalarm_positive);
+ }
+ if (watch->xalarm_negative != None) {
+ XSyncDestroyAlarm (GDK_DISPLAY (), watch->xalarm_negative);
+ }
+ g_slice_free (GSIdleMonitorWatch, watch);
+}
+
+static void
+gs_idle_monitor_init (GSIdleMonitor *monitor)
+{
+ monitor->priv = GS_IDLE_MONITOR_GET_PRIVATE (monitor);
+
+ monitor->priv->watches = g_hash_table_new_full (NULL,
+ NULL,
+ NULL,
+ (GDestroyNotify)idle_monitor_watch_free);
+
+ monitor->priv->counter = None;
+}
+
+static void
+gs_idle_monitor_finalize (GObject *object)
+{
+ GSIdleMonitor *idle_monitor;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GS_IS_IDLE_MONITOR (object));
+
+ idle_monitor = GS_IDLE_MONITOR (object);
+
+ g_return_if_fail (idle_monitor->priv != NULL);
+
+ G_OBJECT_CLASS (gs_idle_monitor_parent_class)->finalize (object);
+}
+
+GSIdleMonitor *
+gs_idle_monitor_new (void)
+{
+ GObject *idle_monitor;
+
+ idle_monitor = g_object_new (GS_TYPE_IDLE_MONITOR,
+ NULL);
+
+ return GS_IDLE_MONITOR (idle_monitor);
+}
+
+static gboolean
+_xsync_alarm_set (GSIdleMonitor *monitor,
+ GSIdleMonitorWatch *watch)
+{
+ XSyncAlarmAttributes attr;
+ XSyncValue delta;
+ guint flags;
+
+ flags = XSyncCACounter
+ | XSyncCAValueType
+ | XSyncCATestType
+ | XSyncCAValue
+ | XSyncCADelta
+ | XSyncCAEvents;
+
+ XSyncIntToValue (&delta, 0);
+ attr.trigger.counter = monitor->priv->counter;
+ attr.trigger.value_type = XSyncAbsolute;
+ attr.trigger.wait_value = watch->interval;
+ attr.delta = delta;
+ attr.events = TRUE;
+
+ attr.trigger.test_type = XSyncPositiveTransition;
+ if (watch->xalarm_positive != None) {
+ g_debug ("GSIdleMonitor: updating alarm for positive transition wait=%lld",
+ _xsyncvalue_to_int64 (attr.trigger.wait_value));
+ XSyncChangeAlarm (GDK_DISPLAY (), watch->xalarm_positive, flags, &attr);
+ } else {
+ g_debug ("GSIdleMonitor: creating new alarm for positive transition wait=%lld",
+ _xsyncvalue_to_int64 (attr.trigger.wait_value));
+ watch->xalarm_positive = XSyncCreateAlarm (GDK_DISPLAY (), flags, &attr);
+ }
+
+ attr.trigger.test_type = XSyncNegativeTransition;
+ if (watch->xalarm_negative != None) {
+ g_debug ("GSIdleMonitor: updating alarm for negative transition wait=%lld",
+ _xsyncvalue_to_int64 (attr.trigger.wait_value));
+ XSyncChangeAlarm (GDK_DISPLAY (), watch->xalarm_negative, flags, &attr);
+ } else {
+ g_debug ("GSIdleMonitor: creating new alarm for negative transition wait=%lld",
+ _xsyncvalue_to_int64 (attr.trigger.wait_value));
+ watch->xalarm_negative = XSyncCreateAlarm (GDK_DISPLAY (), flags, &attr);
+ }
+
+ return TRUE;
+}
+
+guint
+gs_idle_monitor_add_watch (GSIdleMonitor *monitor,
+ guint interval,
+ GSIdleMonitorWatchFunc callback,
+ gpointer user_data)
+{
+ GSIdleMonitorWatch *watch;
+
+ g_return_val_if_fail (GS_IS_IDLE_MONITOR (monitor), 0);
+ g_return_val_if_fail (callback != NULL, 0);
+
+ watch = idle_monitor_watch_new (interval);
+ watch->callback = callback;
+ watch->user_data = user_data;
+
+ _xsync_alarm_set (monitor, watch);
+
+ g_hash_table_insert (monitor->priv->watches,
+ GUINT_TO_POINTER (watch->id),
+ watch);
+ return watch->id;
+}
+
+void
+gs_idle_monitor_remove_watch (GSIdleMonitor *monitor,
+ guint id)
+{
+ g_return_if_fail (GS_IS_IDLE_MONITOR (monitor));
+
+ g_hash_table_remove (monitor->priv->watches,
+ GUINT_TO_POINTER (id));
+}