/* -*- 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 St, Fifth Floor, Boston, MA 02110-1301, USA. * * Authors: William Jon McCann <mccann@jhu.edu> * */ #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_XDISPLAY(gdk_display_get_default())); XTestFakeKeyEvent (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), *monitor->priv->keycode, True, CurrentTime); XTestFakeKeyEvent (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), *monitor->priv->keycode, False, CurrentTime); XUnlockDisplay (GDK_DISPLAY_XDISPLAY(gdk_display_get_default())); /* 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 = %ld", 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_XDISPLAY(gdk_display_get_default()), &monitor->priv->sync_event_base, &sync_error_base); if (! res) { g_warning ("GSIdleMonitor: Sync extension not present"); return FALSE; } res = XSyncInitialize (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), &major, &minor); if (! res) { g_warning ("GSIdleMonitor: Unable to initialize Sync extension"); return FALSE; } counters = XSyncListSystemCounters (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), &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_XDISPLAY(gdk_display_get_default())); monitor->priv->have_xtest = (XTestQueryExtension (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), &a, &b, &c, &d) == True); if (monitor->priv->have_xtest) { monitor->priv->keycode1 = XKeysymToKeycode (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), XK_Alt_L); if (monitor->priv->keycode1 == 0) { g_warning ("keycode1 not existant"); } monitor->priv->keycode2 = XKeysymToKeycode (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), XK_Alt_R); if (monitor->priv->keycode2 == 0) { monitor->priv->keycode2 = XKeysymToKeycode (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), XK_Alt_L); if (monitor->priv->keycode2 == 0) { g_warning ("keycode2 not existant"); } } monitor->priv->keycode = &monitor->priv->keycode1; } XUnlockDisplay (GDK_DISPLAY_XDISPLAY(gdk_display_get_default())); #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_XDISPLAY(gdk_display_get_default()), watch->xalarm_positive); } if (watch->xalarm_negative != None) { XSyncDestroyAlarm (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), 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=%ld", _xsyncvalue_to_int64 (attr.trigger.wait_value)); XSyncChangeAlarm (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), watch->xalarm_positive, flags, &attr); } else { g_debug ("GSIdleMonitor: creating new alarm for positive transition wait=%ld", _xsyncvalue_to_int64 (attr.trigger.wait_value)); watch->xalarm_positive = XSyncCreateAlarm (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), flags, &attr); } attr.trigger.test_type = XSyncNegativeTransition; if (watch->xalarm_negative != None) { g_debug ("GSIdleMonitor: updating alarm for negative transition wait=%ld", _xsyncvalue_to_int64 (attr.trigger.wait_value)); XSyncChangeAlarm (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), watch->xalarm_negative, flags, &attr); } else { g_debug ("GSIdleMonitor: creating new alarm for negative transition wait=%ld", _xsyncvalue_to_int64 (attr.trigger.wait_value)); watch->xalarm_negative = XSyncCreateAlarm (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), 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)); }