1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2007-2011 Richard Hughes <richard@hughsie.com>
4  *
5  * Licensed under the GNU General Public License Version 2
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25 
26 #include <glib.h>
27 #include <X11/Xlib.h>
28 #include <X11/extensions/sync.h>
29 #include <gdk/gdkx.h>
30 #include <gdk/gdk.h>
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 
36 #include "gpm-idletime.h"
37 
38 static void     gpm_idletime_finalize   (GObject       *object);
39 
40 #define GPM_IDLETIME_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GPM_IDLETIME_TYPE, GpmIdletimePrivate))
41 
42 struct GpmIdletimePrivate
43 {
44         gint                     sync_event;
45         gboolean                 reset_set;
46         XSyncCounter             idle_counter;
47         GPtrArray               *array;
48         Display                 *dpy;
49 };
50 
51 typedef struct
52 {
53         guint                    id;
54         XSyncValue               timeout;
55         XSyncAlarm               xalarm;
56         GpmIdletime             *idletime;
57 } GpmIdletimeAlarm;
58 
59 enum {
60         SIGNAL_ALARM_EXPIRED,
61         SIGNAL_RESET,
62         LAST_SIGNAL
63 };
64 
65 typedef enum {
66         GPM_IDLETIME_ALARM_TYPE_POSITIVE,
67         GPM_IDLETIME_ALARM_TYPE_NEGATIVE,
68         GPM_IDLETIME_ALARM_TYPE_DISABLED
69 } GpmIdletimeAlarmType;
70 
71 static guint signals [LAST_SIGNAL] = { 0 };
72 
G_DEFINE_TYPE(GpmIdletime,gpm_idletime,G_TYPE_OBJECT)73 G_DEFINE_TYPE (GpmIdletime, gpm_idletime, G_TYPE_OBJECT)
74 
75 static gint64
76 gpm_idletime_xsyncvalue_to_int64 (XSyncValue value)
77 {
78         return ((guint64) XSyncValueHigh32 (value)) << 32 |
79                 (guint64) XSyncValueLow32 (value);
80 }
81 
82 /* gets the IDLETIME counter value, or 0 for invalid */
83 gint64
gpm_idletime_get_time(GpmIdletime * idletime)84 gpm_idletime_get_time (GpmIdletime *idletime)
85 {
86         XSyncValue value;
87 
88         /* we don't have IDLETIME support */
89         if (!idletime->priv->idle_counter)
90                 return 0;
91 
92         /* NX explodes if you query the counter */
93         gdk_x11_display_error_trap_push (gdk_display_get_default ());
94         XSyncQueryCounter (idletime->priv->dpy,
95                            idletime->priv->idle_counter,
96                            &value);
97         if (gdk_x11_display_error_trap_pop (gdk_display_get_default ()))
98                 return 0;
99         return gpm_idletime_xsyncvalue_to_int64 (value);
100 }
101 
102 static void
gpm_idletime_xsync_alarm_set(GpmIdletime * idletime,GpmIdletimeAlarm * alarm_item,GpmIdletimeAlarmType alarm_type)103 gpm_idletime_xsync_alarm_set (GpmIdletime *idletime,
104                               GpmIdletimeAlarm *alarm_item,
105                               GpmIdletimeAlarmType alarm_type)
106 {
107         XSyncAlarmAttributes attr;
108         XSyncValue delta;
109         unsigned int flags;
110         XSyncTestType test;
111 
112         /* just remove it */
113         if (alarm_type == GPM_IDLETIME_ALARM_TYPE_DISABLED) {
114                 if (alarm_item->xalarm) {
115                         XSyncDestroyAlarm (idletime->priv->dpy,
116                                            alarm_item->xalarm);
117                         alarm_item->xalarm = None;
118                 }
119                 return;
120         }
121 
122         /* which way do we do the test? */
123         if (alarm_type == GPM_IDLETIME_ALARM_TYPE_POSITIVE)
124                 test = XSyncPositiveTransition;
125         else
126                 test = XSyncNegativeTransition;
127 
128         XSyncIntToValue (&delta, 0);
129 
130         attr.trigger.counter = idletime->priv->idle_counter;
131         attr.trigger.value_type = XSyncAbsolute;
132         attr.trigger.test_type = test;
133         attr.trigger.wait_value = alarm_item->timeout;
134         attr.delta = delta;
135 
136         flags = XSyncCACounter |
137                 XSyncCAValueType |
138                 XSyncCATestType |
139                 XSyncCAValue |
140                 XSyncCADelta;
141 
142         if (alarm_item->xalarm) {
143                 XSyncChangeAlarm (idletime->priv->dpy,
144                                   alarm_item->xalarm,
145                                   flags,
146                                   &attr);
147         } else {
148                 alarm_item->xalarm = XSyncCreateAlarm (idletime->priv->dpy,
149                                                        flags,
150                                                        &attr);
151         }
152 }
153 
154 void
gpm_idletime_alarm_reset_all(GpmIdletime * idletime)155 gpm_idletime_alarm_reset_all (GpmIdletime *idletime)
156 {
157         guint i;
158         GpmIdletimeAlarm *alarm_item;
159 
160         g_return_if_fail (GPM_IS_IDLETIME (idletime));
161 
162         if (!idletime->priv->reset_set)
163                 return;
164 
165         /* reset all the alarms (except the reset alarm) to their timeouts */
166         for (i=1; i < idletime->priv->array->len; i++) {
167                 alarm_item = g_ptr_array_index (idletime->priv->array, i);
168                 gpm_idletime_xsync_alarm_set (idletime,
169                                               alarm_item,
170                                               GPM_IDLETIME_ALARM_TYPE_POSITIVE);
171         }
172 
173         /* set the reset alarm to be disabled */
174         alarm_item = g_ptr_array_index (idletime->priv->array, 0);
175         gpm_idletime_xsync_alarm_set (idletime,
176                                       alarm_item,
177                                       GPM_IDLETIME_ALARM_TYPE_DISABLED);
178 
179         /* emit signal so say we've reset all timers */
180         g_signal_emit (idletime, signals [SIGNAL_RESET], 0);
181 
182         /* we need to be reset again on the next event */
183         idletime->priv->reset_set = FALSE;
184 }
185 
186 static GpmIdletimeAlarm *
gpm_idletime_alarm_find_id(GpmIdletime * idletime,guint id)187 gpm_idletime_alarm_find_id (GpmIdletime *idletime, guint id)
188 {
189         guint i;
190         GpmIdletimeAlarm *alarm_item;
191         for (i = 0; i < idletime->priv->array->len; i++) {
192                 alarm_item = g_ptr_array_index (idletime->priv->array, i);
193                 if (alarm_item->id == id)
194                         return alarm_item;
195         }
196         return NULL;
197 }
198 
199 static void
gpm_idletime_set_reset_alarm(GpmIdletime * idletime,XSyncAlarmNotifyEvent * alarm_event)200 gpm_idletime_set_reset_alarm (GpmIdletime *idletime,
201                               XSyncAlarmNotifyEvent *alarm_event)
202 {
203         GpmIdletimeAlarm *alarm_item;
204         int overflow;
205         XSyncValue add;
206         gint64 current, reset_threshold;
207 
208         alarm_item = gpm_idletime_alarm_find_id (idletime, 0);
209 
210         if (!idletime->priv->reset_set) {
211                 /* don't match on the current value because
212                  * XSyncNegativeComparison means less or equal. */
213                 XSyncIntToValue (&add, -1);
214                 XSyncValueAdd (&alarm_item->timeout,
215                               alarm_event->counter_value,
216                               add,
217                               &overflow);
218 
219                 /* set the reset alarm to fire the next time
220                  * idletime->priv->idle_counter < the current counter value */
221                 gpm_idletime_xsync_alarm_set (idletime,
222                                               alarm_item,
223                                               GPM_IDLETIME_ALARM_TYPE_NEGATIVE);
224 
225                 /* don't try to set this again if multiple timers are
226                  * going off in sequence */
227                 idletime->priv->reset_set = TRUE;
228 
229                 current = gpm_idletime_get_time (idletime);
230                 reset_threshold = gpm_idletime_xsyncvalue_to_int64 (alarm_item->timeout);
231                 if (current < reset_threshold) {
232                         /* We've missed the alarm already */
233                         gpm_idletime_alarm_reset_all (idletime);
234                 }
235         }
236 }
237 
238 static GpmIdletimeAlarm *
gpm_idletime_alarm_find_event(GpmIdletime * idletime,XSyncAlarmNotifyEvent * alarm_event)239 gpm_idletime_alarm_find_event (GpmIdletime *idletime,
240                                XSyncAlarmNotifyEvent *alarm_event)
241 {
242         guint i;
243         GpmIdletimeAlarm *alarm_item;
244         for (i = 0; i < idletime->priv->array->len; i++) {
245                 alarm_item = g_ptr_array_index (idletime->priv->array, i);
246                 if (alarm_event->alarm == alarm_item->xalarm)
247                         return alarm_item;
248         }
249         return NULL;
250 }
251 
252 static GdkFilterReturn
gpm_idletime_event_filter_cb(GdkXEvent * gdkxevent,GdkEvent * event,gpointer data)253 gpm_idletime_event_filter_cb (GdkXEvent *gdkxevent,
254                               GdkEvent *event,
255                               gpointer data)
256 {
257         GpmIdletimeAlarm *alarm_item;
258         XEvent *xevent = (XEvent *) gdkxevent;
259         GpmIdletime *idletime = (GpmIdletime *) data;
260         XSyncAlarmNotifyEvent *alarm_event;
261 
262         /* no point continuing */
263         if (xevent->type != idletime->priv->sync_event + XSyncAlarmNotify)
264                 return GDK_FILTER_CONTINUE;
265 
266         alarm_event = (XSyncAlarmNotifyEvent *) xevent;
267 
268         /* did we match one of our alarms? */
269         alarm_item = gpm_idletime_alarm_find_event (idletime, alarm_event);
270         if (alarm_item == NULL)
271                 return GDK_FILTER_CONTINUE;
272 
273         /* are we the reset alarm? */
274         if (alarm_item->id == 0) {
275                 gpm_idletime_alarm_reset_all (idletime);
276                 goto out;
277         }
278 
279         /* emit */
280         g_signal_emit (alarm_item->idletime,
281                        signals[SIGNAL_ALARM_EXPIRED],
282                        0, alarm_item->id);
283 
284         /* we need the first alarm to go off to set the reset alarm */
285         gpm_idletime_set_reset_alarm (idletime, alarm_event);
286 out:
287         /* don't propagate */
288         return GDK_FILTER_REMOVE;
289 }
290 
291 static GpmIdletimeAlarm *
gpm_idletime_alarm_new(GpmIdletime * idletime,guint id)292 gpm_idletime_alarm_new (GpmIdletime *idletime, guint id)
293 {
294         GpmIdletimeAlarm *alarm_item;
295 
296         /* create a new alarm */
297         alarm_item = g_new0 (GpmIdletimeAlarm, 1);
298 
299         /* set the default values */
300         alarm_item->id = id;
301         alarm_item->xalarm = None;
302         alarm_item->idletime = g_object_ref (idletime);
303 
304         return alarm_item;
305 }
306 
307 gboolean
gpm_idletime_alarm_set(GpmIdletime * idletime,guint id,guint timeout)308 gpm_idletime_alarm_set (GpmIdletime *idletime,
309                         guint id,
310                         guint timeout)
311 {
312         GpmIdletimeAlarm *alarm_item;
313 
314         g_return_val_if_fail (GPM_IS_IDLETIME (idletime), FALSE);
315         g_return_val_if_fail (id != 0, FALSE);
316 
317         if (timeout == 0) {
318                 gpm_idletime_alarm_remove (idletime, id);
319                 return FALSE;
320         }
321 
322         /* see if we already created an alarm with this ID */
323         alarm_item = gpm_idletime_alarm_find_id (idletime, id);
324         if (alarm_item == NULL) {
325                 /* create a new alarm */
326                 alarm_item = gpm_idletime_alarm_new (idletime, id);
327                 g_ptr_array_add (idletime->priv->array, alarm_item);
328         }
329 
330         /* set the timeout */
331         XSyncIntToValue (&alarm_item->timeout, (gint)timeout);
332 
333         /* set, and start the timer */
334         gpm_idletime_xsync_alarm_set (idletime,
335                                       alarm_item,
336                                       GPM_IDLETIME_ALARM_TYPE_POSITIVE);
337         return TRUE;
338 }
339 
340 static gboolean
gpm_idletime_alarm_free(GpmIdletime * idletime,GpmIdletimeAlarm * alarm_item)341 gpm_idletime_alarm_free (GpmIdletime *idletime,
342                          GpmIdletimeAlarm *alarm_item)
343 {
344         g_return_val_if_fail (GPM_IS_IDLETIME (idletime), FALSE);
345         g_return_val_if_fail (alarm_item != NULL, FALSE);
346 
347         if (alarm_item->xalarm) {
348                 XSyncDestroyAlarm (idletime->priv->dpy,
349                                    alarm_item->xalarm);
350         }
351         g_object_unref (alarm_item->idletime);
352         g_free (alarm_item);
353         g_ptr_array_remove (idletime->priv->array, alarm_item);
354         return TRUE;
355 }
356 
357 gboolean
gpm_idletime_alarm_remove(GpmIdletime * idletime,guint id)358 gpm_idletime_alarm_remove (GpmIdletime *idletime, guint id)
359 {
360         GpmIdletimeAlarm *alarm_item;
361 
362         g_return_val_if_fail (GPM_IS_IDLETIME (idletime), FALSE);
363 
364         alarm_item = gpm_idletime_alarm_find_id (idletime, id);
365         if (alarm_item == NULL)
366                 return FALSE;
367         gpm_idletime_alarm_free (idletime, alarm_item);
368         return TRUE;
369 }
370 
371 static void
gpm_idletime_class_init(GpmIdletimeClass * klass)372 gpm_idletime_class_init (GpmIdletimeClass *klass)
373 {
374         GObjectClass *object_class = G_OBJECT_CLASS (klass);
375         object_class->finalize = gpm_idletime_finalize;
376         g_type_class_add_private (klass, sizeof (GpmIdletimePrivate));
377 
378         signals [SIGNAL_ALARM_EXPIRED] =
379                 g_signal_new ("alarm-expired",
380                               G_TYPE_FROM_CLASS (object_class),
381                               G_SIGNAL_RUN_LAST,
382                               G_STRUCT_OFFSET (GpmIdletimeClass, alarm_expired),
383                               NULL, NULL, g_cclosure_marshal_VOID__UINT,
384                               G_TYPE_NONE, 1, G_TYPE_UINT);
385         signals [SIGNAL_RESET] =
386                 g_signal_new ("reset",
387                               G_TYPE_FROM_CLASS (object_class),
388                               G_SIGNAL_RUN_LAST,
389                               G_STRUCT_OFFSET (GpmIdletimeClass, reset),
390                               NULL, NULL, g_cclosure_marshal_VOID__VOID,
391                               G_TYPE_NONE, 0);
392 }
393 
394 static void
gpm_idletime_init(GpmIdletime * idletime)395 gpm_idletime_init (GpmIdletime *idletime)
396 {
397         int sync_error;
398         int ncounters;
399         XSyncSystemCounter *counters;
400         GpmIdletimeAlarm *alarm_item;
401         gint major, minor;
402         guint i;
403 
404         idletime->priv = GPM_IDLETIME_GET_PRIVATE (idletime);
405 
406         idletime->priv->array = g_ptr_array_new ();
407 
408         idletime->priv->reset_set = FALSE;
409         idletime->priv->idle_counter = None;
410         idletime->priv->sync_event = 0;
411         idletime->priv->dpy = GDK_DISPLAY_XDISPLAY (gdk_display_get_default());
412 
413         /* get the sync event */
414         if (!XSyncQueryExtension (idletime->priv->dpy,
415                                   &idletime->priv->sync_event,
416                                   &sync_error)) {
417                 g_warning ("No Sync extension.");
418                 return;
419         }
420 
421         /* check XSync is compatible with the server version */
422         if (!XSyncInitialize (idletime->priv->dpy, &major, &minor)) {
423                 g_warning ("Sync extension not compatible.");
424                 return;
425         }
426         counters = XSyncListSystemCounters (idletime->priv->dpy,
427                                             &ncounters);
428         for (i = 0; i < ncounters && !idletime->priv->idle_counter; i++) {
429                 if (strcmp(counters[i].name, "IDLETIME") == 0)
430                         idletime->priv->idle_counter = counters[i].counter;
431         }
432         XSyncFreeSystemCounterList (counters);
433 
434         /* arh. we don't have IDLETIME support */
435         if (!idletime->priv->idle_counter) {
436                 g_warning ("No idle counter");
437                 return;
438         }
439 
440         /* catch the timer alarm */
441         gdk_window_add_filter (NULL,
442                                gpm_idletime_event_filter_cb,
443                                idletime);
444 
445         /* create a reset alarm */
446         alarm_item = gpm_idletime_alarm_new (idletime, 0);
447         g_ptr_array_add (idletime->priv->array, alarm_item);
448 }
449 
450 static void
gpm_idletime_finalize(GObject * object)451 gpm_idletime_finalize (GObject *object)
452 {
453         guint i;
454         GpmIdletime *idletime;
455         GpmIdletimeAlarm *alarm_item;
456 
457         g_return_if_fail (object != NULL);
458         g_return_if_fail (GPM_IS_IDLETIME (object));
459 
460         idletime = GPM_IDLETIME (object);
461         idletime->priv = GPM_IDLETIME_GET_PRIVATE (idletime);
462 
463         /* remove filter */
464         gdk_window_remove_filter (NULL,
465                                   gpm_idletime_event_filter_cb,
466                                   idletime);
467 
468         /* free all counters, including reset counter */
469         for (i = 0; i < idletime->priv->array->len; i++) {
470                 alarm_item = g_ptr_array_index (idletime->priv->array, i);
471                 gpm_idletime_alarm_free (idletime, alarm_item);
472         }
473         g_ptr_array_free (idletime->priv->array, TRUE);
474 
475         G_OBJECT_CLASS (gpm_idletime_parent_class)->finalize (object);
476 }
477 
478 GpmIdletime *
gpm_idletime_new(void)479 gpm_idletime_new (void)
480 {
481         return g_object_new (GPM_IDLETIME_TYPE, NULL);
482 }
483 
484