1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2
3 /*
4 * Copyright 2013 Red Hat, Inc.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
18 *
19 * Adapted from gnome-session/gnome-session/gs-idle-monitor.c and
20 * from gnome-desktop/libgnome-desktop/gnome-idle-monitor.c
21 */
22
23 /**
24 * SECTION:idle-monitor
25 * @title: MetaIdleMonitor
26 * @short_description: Mutter idle counter (similar to X's IDLETIME)
27 */
28
29 #include "config.h"
30
31 #include <string.h>
32 #include <X11/Xlib.h>
33 #include <X11/extensions/sync.h>
34
35 #include "backends/gsm-inhibitor-flag.h"
36 #include "backends/meta-backend-private.h"
37 #include "backends/meta-idle-monitor-private.h"
38 #include "clutter/clutter.h"
39 #include "meta/main.h"
40 #include "meta/meta-idle-monitor.h"
41 #include "meta/util.h"
42
43 G_STATIC_ASSERT(sizeof(unsigned long) == sizeof(gpointer));
44
45 enum
46 {
47 PROP_0,
48 PROP_DEVICE,
49 PROP_LAST,
50 };
51
52 static GParamSpec *obj_props[PROP_LAST];
53
54 struct _MetaIdleMonitor
55 {
56 GObject parent;
57
58 MetaIdleManager *idle_manager;
59 GDBusProxy *session_proxy;
60 gboolean inhibited;
61 GHashTable *watches;
62 ClutterInputDevice *device;
63 int64_t last_event_time;
64 };
65
G_DEFINE_TYPE(MetaIdleMonitor,meta_idle_monitor,G_TYPE_OBJECT)66 G_DEFINE_TYPE (MetaIdleMonitor, meta_idle_monitor, G_TYPE_OBJECT)
67
68 static void
69 meta_idle_monitor_watch_fire (MetaIdleMonitorWatch *watch)
70 {
71 MetaIdleMonitor *monitor;
72 guint id;
73 gboolean is_user_active_watch;
74
75 monitor = watch->monitor;
76 g_object_ref (monitor);
77
78 g_clear_handle_id (&watch->idle_source_id, g_source_remove);
79
80 id = watch->id;
81 is_user_active_watch = (watch->timeout_msec == 0);
82
83 if (watch->callback)
84 watch->callback (monitor, id, watch->user_data);
85
86 if (is_user_active_watch)
87 meta_idle_monitor_remove_watch (monitor, id);
88
89 g_object_unref (monitor);
90 }
91
92 static void
meta_idle_monitor_dispose(GObject * object)93 meta_idle_monitor_dispose (GObject *object)
94 {
95 MetaIdleMonitor *monitor = META_IDLE_MONITOR (object);
96
97 g_clear_pointer (&monitor->watches, g_hash_table_destroy);
98 g_clear_object (&monitor->session_proxy);
99
100 G_OBJECT_CLASS (meta_idle_monitor_parent_class)->dispose (object);
101 }
102
103 static void
meta_idle_monitor_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)104 meta_idle_monitor_get_property (GObject *object,
105 guint prop_id,
106 GValue *value,
107 GParamSpec *pspec)
108 {
109 MetaIdleMonitor *monitor = META_IDLE_MONITOR (object);
110
111 switch (prop_id)
112 {
113 case PROP_DEVICE:
114 g_value_set_object (value, monitor->device);
115 break;
116 default:
117 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
118 break;
119 }
120 }
121
122 static void
meta_idle_monitor_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)123 meta_idle_monitor_set_property (GObject *object,
124 guint prop_id,
125 const GValue *value,
126 GParamSpec *pspec)
127 {
128 MetaIdleMonitor *monitor = META_IDLE_MONITOR (object);
129 switch (prop_id)
130 {
131 case PROP_DEVICE:
132 monitor->device = g_value_get_object (value);
133 break;
134 default:
135 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
136 break;
137 }
138 }
139
140 static void
meta_idle_monitor_class_init(MetaIdleMonitorClass * klass)141 meta_idle_monitor_class_init (MetaIdleMonitorClass *klass)
142 {
143 GObjectClass *object_class = G_OBJECT_CLASS (klass);
144
145 object_class->dispose = meta_idle_monitor_dispose;
146 object_class->get_property = meta_idle_monitor_get_property;
147 object_class->set_property = meta_idle_monitor_set_property;
148
149 /**
150 * MetaIdleMonitor:device:
151 *
152 * The device to listen to idletime on.
153 */
154 obj_props[PROP_DEVICE] =
155 g_param_spec_object ("device",
156 "Device",
157 "The device to listen to idletime on",
158 CLUTTER_TYPE_INPUT_DEVICE,
159 G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
160
161 g_object_class_install_property (object_class, PROP_DEVICE, obj_props[PROP_DEVICE]);
162 }
163
164 static void
free_watch(gpointer data)165 free_watch (gpointer data)
166 {
167 MetaIdleMonitorWatch *watch = (MetaIdleMonitorWatch *) data;
168 MetaIdleMonitor *monitor = watch->monitor;
169
170 g_object_ref (monitor);
171
172 g_clear_handle_id (&watch->idle_source_id, g_source_remove);
173
174 if (watch->notify != NULL)
175 watch->notify (watch->user_data);
176
177 if (watch->timeout_source != NULL)
178 g_source_destroy (watch->timeout_source);
179
180 g_object_unref (monitor);
181 g_free (watch);
182 }
183
184 static void
update_inhibited_watch(gpointer key,gpointer value,gpointer user_data)185 update_inhibited_watch (gpointer key,
186 gpointer value,
187 gpointer user_data)
188 {
189 MetaIdleMonitor *monitor = user_data;
190 MetaIdleMonitorWatch *watch = value;
191
192 if (!watch->timeout_source)
193 return;
194
195 if (monitor->inhibited)
196 {
197 g_source_set_ready_time (watch->timeout_source, -1);
198 }
199 else
200 {
201 g_source_set_ready_time (watch->timeout_source,
202 monitor->last_event_time +
203 watch->timeout_msec * 1000);
204 }
205 }
206
207 static void
update_inhibited(MetaIdleMonitor * monitor,gboolean inhibited)208 update_inhibited (MetaIdleMonitor *monitor,
209 gboolean inhibited)
210 {
211 if (inhibited == monitor->inhibited)
212 return;
213
214 monitor->inhibited = inhibited;
215
216 g_hash_table_foreach (monitor->watches,
217 update_inhibited_watch,
218 monitor);
219 }
220
221 static void
meta_idle_monitor_inhibited_actions_changed(GDBusProxy * session,GVariant * changed,char ** invalidated,gpointer user_data)222 meta_idle_monitor_inhibited_actions_changed (GDBusProxy *session,
223 GVariant *changed,
224 char **invalidated,
225 gpointer user_data)
226 {
227 MetaIdleMonitor *monitor = user_data;
228 GVariant *v;
229
230 v = g_variant_lookup_value (changed, "InhibitedActions", G_VARIANT_TYPE_UINT32);
231 if (v)
232 {
233 gboolean inhibited;
234
235 inhibited = !!(g_variant_get_uint32 (v) & GSM_INHIBITOR_FLAG_IDLE);
236 g_variant_unref (v);
237
238 if (!inhibited)
239 monitor->last_event_time = g_get_monotonic_time ();
240 update_inhibited (monitor, inhibited);
241 }
242 }
243
244 static void
meta_idle_monitor_init(MetaIdleMonitor * monitor)245 meta_idle_monitor_init (MetaIdleMonitor *monitor)
246 {
247 GVariant *v;
248
249 monitor->watches = g_hash_table_new_full (NULL, NULL, NULL, free_watch);
250 monitor->last_event_time = g_get_monotonic_time ();
251
252 /* Monitor inhibitors */
253 monitor->session_proxy =
254 g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
255 G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
256 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
257 NULL,
258 "org.gnome.SessionManager",
259 "/org/gnome/SessionManager",
260 "org.gnome.SessionManager",
261 NULL,
262 NULL);
263 if (!monitor->session_proxy)
264 return;
265
266 g_signal_connect (monitor->session_proxy, "g-properties-changed",
267 G_CALLBACK (meta_idle_monitor_inhibited_actions_changed),
268 monitor);
269
270 v = g_dbus_proxy_get_cached_property (monitor->session_proxy,
271 "InhibitedActions");
272 if (v)
273 {
274 monitor->inhibited = !!(g_variant_get_uint32 (v) &
275 GSM_INHIBITOR_FLAG_IDLE);
276 g_variant_unref (v);
277 }
278 }
279
280 static guint32
get_next_watch_serial(void)281 get_next_watch_serial (void)
282 {
283 static guint32 serial = 0;
284
285 g_atomic_int_inc (&serial);
286
287 return serial;
288 }
289
290 static gboolean
idle_monitor_dispatch_timeout(GSource * source,GSourceFunc callback,gpointer user_data)291 idle_monitor_dispatch_timeout (GSource *source,
292 GSourceFunc callback,
293 gpointer user_data)
294 {
295 MetaIdleMonitorWatch *watch = (MetaIdleMonitorWatch *) user_data;
296 int64_t now;
297 int64_t ready_time;
298
299 now = g_source_get_time (source);
300 ready_time = g_source_get_ready_time (source);
301 if (ready_time > now)
302 return G_SOURCE_CONTINUE;
303
304 g_source_set_ready_time (watch->timeout_source, -1);
305
306 meta_idle_monitor_watch_fire (watch);
307
308 return G_SOURCE_CONTINUE;
309 }
310
311 static GSourceFuncs idle_monitor_source_funcs = {
312 .prepare = NULL,
313 .check = NULL,
314 .dispatch = idle_monitor_dispatch_timeout,
315 .finalize = NULL,
316 };
317
318 static MetaIdleMonitorWatch *
make_watch(MetaIdleMonitor * monitor,guint64 timeout_msec,MetaIdleMonitorWatchFunc callback,gpointer user_data,GDestroyNotify notify)319 make_watch (MetaIdleMonitor *monitor,
320 guint64 timeout_msec,
321 MetaIdleMonitorWatchFunc callback,
322 gpointer user_data,
323 GDestroyNotify notify)
324 {
325 MetaIdleMonitorWatch *watch;
326
327 watch = g_new0 (MetaIdleMonitorWatch, 1);
328
329 watch->monitor = monitor;
330 watch->id = get_next_watch_serial ();
331 watch->callback = callback;
332 watch->user_data = user_data;
333 watch->notify = notify;
334 watch->timeout_msec = timeout_msec;
335
336 if (timeout_msec != 0)
337 {
338 GSource *source = g_source_new (&idle_monitor_source_funcs,
339 sizeof (GSource));
340
341 g_source_set_callback (source, NULL, watch, NULL);
342 if (!monitor->inhibited)
343 {
344 g_source_set_ready_time (source,
345 monitor->last_event_time +
346 timeout_msec * 1000);
347 }
348 g_source_attach (source, NULL);
349 g_source_unref (source);
350
351 watch->timeout_source = source;
352 }
353
354 g_hash_table_insert (monitor->watches,
355 GUINT_TO_POINTER (watch->id),
356 watch);
357 return watch;
358 }
359
360 /**
361 * meta_idle_monitor_add_idle_watch:
362 * @monitor: A #MetaIdleMonitor
363 * @interval_msec: The idletime interval, in milliseconds
364 * @callback: (nullable): The callback to call when the user has
365 * accumulated @interval_msec milliseconds of idle time.
366 * @user_data: (nullable): The user data to pass to the callback
367 * @notify: A #GDestroyNotify
368 *
369 * Returns: a watch id
370 *
371 * Adds a watch for a specific idle time. The callback will be called
372 * when the user has accumulated @interval_msec milliseconds of idle time.
373 * This function will return an ID that can either be passed to
374 * meta_idle_monitor_remove_watch(), or can be used to tell idle time
375 * watches apart if you have more than one.
376 *
377 * Also note that this function will only care about positive transitions
378 * (user's idle time exceeding a certain time). If you want to know about
379 * when the user has become active, use
380 * meta_idle_monitor_add_user_active_watch().
381 */
382 guint
meta_idle_monitor_add_idle_watch(MetaIdleMonitor * monitor,guint64 interval_msec,MetaIdleMonitorWatchFunc callback,gpointer user_data,GDestroyNotify notify)383 meta_idle_monitor_add_idle_watch (MetaIdleMonitor *monitor,
384 guint64 interval_msec,
385 MetaIdleMonitorWatchFunc callback,
386 gpointer user_data,
387 GDestroyNotify notify)
388 {
389 MetaIdleMonitorWatch *watch;
390
391 g_return_val_if_fail (META_IS_IDLE_MONITOR (monitor), 0);
392 g_return_val_if_fail (interval_msec > 0, 0);
393
394 watch = make_watch (monitor,
395 interval_msec,
396 callback,
397 user_data,
398 notify);
399
400 return watch->id;
401 }
402
403 /**
404 * meta_idle_monitor_add_user_active_watch:
405 * @monitor: A #MetaIdleMonitor
406 * @callback: (nullable): The callback to call when the user is
407 * active again.
408 * @user_data: (nullable): The user data to pass to the callback
409 * @notify: A #GDestroyNotify
410 *
411 * Returns: a watch id
412 *
413 * Add a one-time watch to know when the user is active again.
414 * Note that this watch is one-time and will de-activate after the
415 * function is called, for efficiency purposes. It's most convenient
416 * to call this when an idle watch, as added by
417 * meta_idle_monitor_add_idle_watch(), has triggered.
418 */
419 guint
meta_idle_monitor_add_user_active_watch(MetaIdleMonitor * monitor,MetaIdleMonitorWatchFunc callback,gpointer user_data,GDestroyNotify notify)420 meta_idle_monitor_add_user_active_watch (MetaIdleMonitor *monitor,
421 MetaIdleMonitorWatchFunc callback,
422 gpointer user_data,
423 GDestroyNotify notify)
424 {
425 MetaIdleMonitorWatch *watch;
426
427 g_return_val_if_fail (META_IS_IDLE_MONITOR (monitor), 0);
428
429 watch = make_watch (monitor,
430 0,
431 callback,
432 user_data,
433 notify);
434
435 return watch->id;
436 }
437
438 /**
439 * meta_idle_monitor_remove_watch:
440 * @monitor: A #MetaIdleMonitor
441 * @id: A watch ID
442 *
443 * Removes an idle time watcher, previously added by
444 * meta_idle_monitor_add_idle_watch() or
445 * meta_idle_monitor_add_user_active_watch().
446 */
447 void
meta_idle_monitor_remove_watch(MetaIdleMonitor * monitor,guint id)448 meta_idle_monitor_remove_watch (MetaIdleMonitor *monitor,
449 guint id)
450 {
451 g_return_if_fail (META_IS_IDLE_MONITOR (monitor));
452
453 g_object_ref (monitor);
454 g_hash_table_remove (monitor->watches,
455 GUINT_TO_POINTER (id));
456 g_object_unref (monitor);
457 }
458
459 /**
460 * meta_idle_monitor_get_idletime:
461 * @monitor: A #MetaIdleMonitor
462 *
463 * Returns: The current idle time, in milliseconds, or -1 for not supported
464 */
465 gint64
meta_idle_monitor_get_idletime(MetaIdleMonitor * monitor)466 meta_idle_monitor_get_idletime (MetaIdleMonitor *monitor)
467 {
468 return (g_get_monotonic_time () - monitor->last_event_time) / 1000;
469 }
470
471 void
meta_idle_monitor_reset_idletime(MetaIdleMonitor * monitor)472 meta_idle_monitor_reset_idletime (MetaIdleMonitor *monitor)
473 {
474 GList *node, *watch_ids;
475
476 monitor->last_event_time = g_get_monotonic_time ();
477
478 watch_ids = g_hash_table_get_keys (monitor->watches);
479
480 for (node = watch_ids; node != NULL; node = node->next)
481 {
482 guint watch_id = GPOINTER_TO_UINT (node->data);
483 MetaIdleMonitorWatch *watch;
484
485 watch = g_hash_table_lookup (monitor->watches,
486 GUINT_TO_POINTER (watch_id));
487 if (!watch)
488 continue;
489
490 if (watch->timeout_msec == 0)
491 {
492 meta_idle_monitor_watch_fire (watch);
493 }
494 else
495 {
496 if (monitor->inhibited)
497 {
498 g_source_set_ready_time (watch->timeout_source, -1);
499 }
500 else
501 {
502 g_source_set_ready_time (watch->timeout_source,
503 monitor->last_event_time +
504 watch->timeout_msec * 1000);
505 }
506 }
507 }
508
509 g_list_free (watch_ids);
510 }
511
512 MetaIdleManager *
meta_idle_monitor_get_manager(MetaIdleMonitor * monitor)513 meta_idle_monitor_get_manager (MetaIdleMonitor *monitor)
514 {
515 return monitor->idle_manager;
516 }
517
518 MetaIdleMonitor *
meta_idle_monitor_new(MetaIdleManager * idle_manager,ClutterInputDevice * device)519 meta_idle_monitor_new (MetaIdleManager *idle_manager,
520 ClutterInputDevice *device)
521 {
522 MetaIdleMonitor *monitor;
523
524 monitor = g_object_new (META_TYPE_IDLE_MONITOR,
525 "device", device,
526 NULL);
527 monitor->idle_manager = idle_manager;
528
529 return monitor;
530 }
531