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