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