8f71920622
If the user is already active when the notification pops up, we won't get an idle watcher because there's no transition from active to idle or vice versa. Correct this by initializing the state correctly from XSync.
430 lines
13 KiB
C
430 lines
13 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
|
|
*
|
|
* Adapted from gnome-session/gnome-session/gs-idle-monitor.c
|
|
*
|
|
* Copyright (C) 2012 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 <mccann@jhu.edu>
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <time.h>
|
|
#include <string.h>
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/extensions/sync.h>
|
|
|
|
#include <glib.h>
|
|
#include <gdk/gdkx.h>
|
|
#include <gdk/gdk.h>
|
|
|
|
#include "shell-idle-monitor.h"
|
|
|
|
#define SHELL_IDLE_MONITOR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SHELL_TYPE_IDLE_MONITOR, ShellIdleMonitorPrivate))
|
|
|
|
struct ShellIdleMonitorPrivate
|
|
{
|
|
Display *display;
|
|
|
|
GHashTable *watches;
|
|
int sync_event_base;
|
|
XSyncCounter counter;
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
Display *display;
|
|
guint id;
|
|
XSyncValue interval;
|
|
ShellIdleMonitorWatchFunc callback;
|
|
gpointer user_data;
|
|
GDestroyNotify notify;
|
|
XSyncAlarm xalarm_positive;
|
|
XSyncAlarm xalarm_negative;
|
|
} ShellIdleMonitorWatch;
|
|
|
|
static guint32 watch_serial = 1;
|
|
|
|
G_DEFINE_TYPE (ShellIdleMonitor, shell_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
|
|
shell_idle_monitor_dispose (GObject *object)
|
|
{
|
|
ShellIdleMonitor *monitor;
|
|
|
|
monitor = SHELL_IDLE_MONITOR (object);
|
|
|
|
if (monitor->priv->watches != NULL) {
|
|
g_hash_table_destroy (monitor->priv->watches);
|
|
monitor->priv->watches = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (shell_idle_monitor_parent_class)->dispose (object);
|
|
}
|
|
|
|
static gboolean
|
|
_find_alarm (gpointer key,
|
|
ShellIdleMonitorWatch *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 ShellIdleMonitorWatch *
|
|
find_watch_for_alarm (ShellIdleMonitor *monitor,
|
|
XSyncAlarm alarm)
|
|
{
|
|
ShellIdleMonitorWatch *watch;
|
|
|
|
watch = g_hash_table_find (monitor->priv->watches,
|
|
(GHRFunc)_find_alarm,
|
|
&alarm);
|
|
return watch;
|
|
}
|
|
|
|
static void
|
|
handle_alarm_notify_event (ShellIdleMonitor *monitor,
|
|
XSyncAlarmNotifyEvent *alarm_event)
|
|
{
|
|
ShellIdleMonitorWatch *watch;
|
|
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 = %" G_GINT64_FORMAT,
|
|
watch->id,
|
|
_xsyncvalue_to_int64 (alarm_event->counter_value)); */
|
|
|
|
if (alarm_event->alarm == watch->xalarm_positive) {
|
|
condition = TRUE;
|
|
} else {
|
|
condition = FALSE;
|
|
}
|
|
|
|
if (watch->callback != NULL) {
|
|
watch->callback (monitor,
|
|
watch->id,
|
|
condition,
|
|
watch->user_data);
|
|
}
|
|
}
|
|
|
|
static GdkFilterReturn
|
|
xevent_filter (GdkXEvent *xevent,
|
|
GdkEvent *event,
|
|
ShellIdleMonitor *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 (ShellIdleMonitor *monitor)
|
|
{
|
|
int sync_error_base;
|
|
int res;
|
|
int major;
|
|
int minor;
|
|
int i;
|
|
int ncounters;
|
|
XSyncSystemCounter *counters;
|
|
|
|
res = XSyncQueryExtension (monitor->priv->display,
|
|
&monitor->priv->sync_event_base,
|
|
&sync_error_base);
|
|
if (! res) {
|
|
g_warning ("ShellIdleMonitor: Sync extension not present");
|
|
return FALSE;
|
|
}
|
|
|
|
res = XSyncInitialize (monitor->priv->display, &major, &minor);
|
|
if (! res) {
|
|
g_warning ("ShellIdleMonitor: Unable to initialize Sync extension");
|
|
return FALSE;
|
|
}
|
|
|
|
counters = XSyncListSystemCounters (monitor->priv->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 ("ShellIdleMonitor: IDLETIME counter not found");
|
|
return FALSE;
|
|
}
|
|
|
|
gdk_window_add_filter (NULL, (GdkFilterFunc)xevent_filter, monitor);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GObject *
|
|
shell_idle_monitor_constructor (GType type,
|
|
guint n_construct_properties,
|
|
GObjectConstructParam *construct_properties)
|
|
{
|
|
ShellIdleMonitor *monitor;
|
|
|
|
monitor = SHELL_IDLE_MONITOR (G_OBJECT_CLASS (shell_idle_monitor_parent_class)->constructor (type,
|
|
n_construct_properties,
|
|
construct_properties));
|
|
|
|
monitor->priv->display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
|
|
|
|
if (! init_xsync (monitor)) {
|
|
g_object_unref (monitor);
|
|
return NULL;
|
|
}
|
|
|
|
return G_OBJECT (monitor);
|
|
}
|
|
|
|
static void
|
|
shell_idle_monitor_class_init (ShellIdleMonitorClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->dispose = shell_idle_monitor_dispose;
|
|
object_class->constructor = shell_idle_monitor_constructor;
|
|
|
|
g_type_class_add_private (klass, sizeof (ShellIdleMonitorPrivate));
|
|
}
|
|
|
|
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 ShellIdleMonitorWatch *
|
|
idle_monitor_watch_new (guint interval)
|
|
{
|
|
ShellIdleMonitorWatch *watch;
|
|
|
|
watch = g_slice_new0 (ShellIdleMonitorWatch);
|
|
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 (ShellIdleMonitorWatch *watch)
|
|
{
|
|
if (watch == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (watch->notify != NULL) {
|
|
watch->notify (watch->user_data);
|
|
}
|
|
|
|
if (watch->xalarm_positive != None) {
|
|
XSyncDestroyAlarm (watch->display, watch->xalarm_positive);
|
|
}
|
|
if (watch->xalarm_negative != None) {
|
|
XSyncDestroyAlarm (watch->display, watch->xalarm_negative);
|
|
}
|
|
g_slice_free (ShellIdleMonitorWatch, watch);
|
|
}
|
|
|
|
static void
|
|
shell_idle_monitor_init (ShellIdleMonitor *monitor)
|
|
{
|
|
monitor->priv = SHELL_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;
|
|
}
|
|
|
|
/**
|
|
* shell_idle_monitor_get:
|
|
*
|
|
* Returns: (transfer none): the global #ShellIdleMonitor.
|
|
*/
|
|
ShellIdleMonitor *
|
|
shell_idle_monitor_get (void)
|
|
{
|
|
static ShellIdleMonitor *idle_monitor;
|
|
|
|
if (G_UNLIKELY (idle_monitor == NULL))
|
|
idle_monitor = g_object_new (SHELL_TYPE_IDLE_MONITOR,
|
|
NULL);
|
|
|
|
return idle_monitor;
|
|
}
|
|
|
|
static gboolean
|
|
_xsync_alarm_set (ShellIdleMonitor *monitor,
|
|
ShellIdleMonitorWatch *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 ("ShellIdleMonitor: updating alarm for positive transition wait=%" G_GINT64_FORMAT,
|
|
_xsyncvalue_to_int64 (attr.trigger.wait_value)); */
|
|
XSyncChangeAlarm (monitor->priv->display, watch->xalarm_positive, flags, &attr);
|
|
} else {
|
|
/* g_debug ("ShellIdleMonitor: creating new alarm for positive transition wait=%" G_GINT64_FORMAT,
|
|
_xsyncvalue_to_int64 (attr.trigger.wait_value)); */
|
|
watch->xalarm_positive = XSyncCreateAlarm (monitor->priv->display, flags, &attr);
|
|
}
|
|
|
|
attr.trigger.wait_value = _int64_to_xsyncvalue (_xsyncvalue_to_int64 (watch->interval) - 1);
|
|
attr.trigger.test_type = XSyncNegativeTransition;
|
|
if (watch->xalarm_negative != None) {
|
|
/* g_debug ("ShellIdleMonitor: updating alarm for negative transition wait=%" G_GINT64_FORMAT,
|
|
_xsyncvalue_to_int64 (attr.trigger.wait_value)); */
|
|
XSyncChangeAlarm (monitor->priv->display, watch->xalarm_negative, flags, &attr);
|
|
} else {
|
|
/* g_debug ("ShellIdleMonitor: creating new alarm for negative transition wait=%" G_GINT64_FORMAT,
|
|
_xsyncvalue_to_int64 (attr.trigger.wait_value)); */
|
|
watch->xalarm_negative = XSyncCreateAlarm (monitor->priv->display, flags, &attr);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
guint
|
|
shell_idle_monitor_add_watch (ShellIdleMonitor *monitor,
|
|
guint interval,
|
|
ShellIdleMonitorWatchFunc callback,
|
|
gpointer user_data,
|
|
GDestroyNotify notify)
|
|
{
|
|
ShellIdleMonitorWatch *watch;
|
|
|
|
g_return_val_if_fail (SHELL_IS_IDLE_MONITOR (monitor), 0);
|
|
g_return_val_if_fail (callback != NULL, 0);
|
|
|
|
watch = idle_monitor_watch_new (interval);
|
|
watch->display = monitor->priv->display;
|
|
watch->callback = callback;
|
|
watch->user_data = user_data;
|
|
watch->notify = notify;
|
|
|
|
_xsync_alarm_set (monitor, watch);
|
|
|
|
g_hash_table_insert (monitor->priv->watches,
|
|
GUINT_TO_POINTER (watch->id),
|
|
watch);
|
|
return watch->id;
|
|
}
|
|
|
|
void
|
|
shell_idle_monitor_remove_watch (ShellIdleMonitor *monitor,
|
|
guint id)
|
|
{
|
|
g_return_if_fail (SHELL_IS_IDLE_MONITOR (monitor));
|
|
|
|
g_hash_table_remove (monitor->priv->watches,
|
|
GUINT_TO_POINTER (id));
|
|
}
|
|
|
|
gint64
|
|
shell_idle_monitor_get_idletime (ShellIdleMonitor *monitor)
|
|
{
|
|
XSyncValue value;
|
|
|
|
if (!XSyncQueryCounter (monitor->priv->display, monitor->priv->counter, &value))
|
|
return FALSE;
|
|
|
|
return _xsyncvalue_to_int64 (value);
|
|
}
|