gnome-idle-monitor.c 17.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/* -*- mode: C; c-file-style: "linux"; indent-tabs-mode: t -*-
 *
 * 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
18
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
19 20 21 22 23 24 25 26 27 28 29 30 31
 */

#include "config.h"

#include <time.h>
#include <string.h>

#include <glib.h>
#include <gdk/gdkx.h>
#include <gdk/gdk.h>

#define GNOME_DESKTOP_USE_UNSTABLE_API
#include "gnome-idle-monitor.h"
32
#include "meta-dbus-idle-monitor.h"
33 34 35

#define GNOME_IDLE_MONITOR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GNOME_TYPE_IDLE_MONITOR, GnomeIdleMonitorPrivate))

36 37
G_STATIC_ASSERT(sizeof(unsigned long) == sizeof(gpointer));

38
struct _GnomeIdleMonitorPrivate
39
{
40 41
	GCancellable        *cancellable;
	MetaDBusIdleMonitor *proxy;
42
	MetaDBusObjectManagerClient *om;
43 44 45 46
	int                  name_watch_id;
	GHashTable          *watches;
	GHashTable          *watches_by_upstream_id;
	GdkDevice           *device;
47
	gchar               *path;
48 49 50 51
};

typedef struct
{
52 53
	int                       ref_count;
	gboolean                  dead;
54
	GnomeIdleMonitor         *monitor;
55
	guint			  id;
56
	guint                     upstream_id;
57 58 59
	GnomeIdleMonitorWatchFunc callback;
	gpointer		  user_data;
	GDestroyNotify		  notify;
60
	guint64                   timeout_msec;
61 62
} GnomeIdleMonitorWatch;

63 64 65 66 67 68 69 70 71
enum
{
	PROP_0,
	PROP_DEVICE,
	PROP_LAST,
};

static GParamSpec *obj_props[PROP_LAST];

72
static void gnome_idle_monitor_initable_iface_init (GInitableIface *iface);
73 74 75 76 77
static void gnome_idle_monitor_remove_watch_internal (GnomeIdleMonitor *monitor,
						      guint             id);

static void add_idle_watch (GnomeIdleMonitor *, GnomeIdleMonitorWatch *);
static void add_active_watch (GnomeIdleMonitor *, GnomeIdleMonitorWatch *);
78 79 80 81

G_DEFINE_TYPE_WITH_CODE (GnomeIdleMonitor, gnome_idle_monitor, G_TYPE_OBJECT,
			 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
						gnome_idle_monitor_initable_iface_init))
82

83
static void
84 85 86
on_watch_fired (MetaDBusIdleMonitor *proxy,
		guint                upstream_id,
		GnomeIdleMonitor    *monitor)
87
{
88
	GnomeIdleMonitorWatch *watch;
89

90 91
	watch = g_hash_table_lookup (monitor->priv->watches_by_upstream_id, GINT_TO_POINTER (upstream_id));
	if (!watch)
92
		return;
93

94
	g_object_ref (monitor);
95

96 97 98 99 100
	if (watch->callback) {
		watch->callback (watch->monitor,
				 watch->id,
				 watch->user_data);
	}
101

102 103
	if (watch->timeout_msec == 0)
		gnome_idle_monitor_remove_watch_internal (monitor, watch->id);
104

105
	g_object_unref (monitor);
106 107 108 109 110
}

static guint32
get_next_watch_serial (void)
{
111 112 113
  static guint32 serial = 0;
  g_atomic_int_inc (&serial);
  return serial;
114 115 116
}

static void
117
idle_monitor_watch_unref (GnomeIdleMonitorWatch *watch)
118
{
119 120 121 122
	watch->ref_count--;
	if (watch->ref_count)
		return;

123
	if (watch->notify != NULL)
124
		watch->notify (watch->user_data);
125 126


127 128 129
	if (watch->upstream_id != 0)
		g_hash_table_remove (watch->monitor->priv->watches_by_upstream_id,
				     GINT_TO_POINTER (watch->upstream_id));
130

131
	g_slice_free (GnomeIdleMonitorWatch, watch);
132 133
}

134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
static GnomeIdleMonitorWatch *
idle_monitor_watch_ref (GnomeIdleMonitorWatch *watch)
{
	g_assert (watch->ref_count > 0);

	watch->ref_count++;
	return watch;
}

static void
idle_monitor_watch_destroy (GnomeIdleMonitorWatch *watch)
{
	watch->dead = TRUE;
	idle_monitor_watch_unref (watch);
}

150 151 152 153 154 155 156
static void
gnome_idle_monitor_dispose (GObject *object)
{
	GnomeIdleMonitor *monitor;

	monitor = GNOME_IDLE_MONITOR (object);

157 158 159
	if (monitor->priv->cancellable)
		g_cancellable_cancel (monitor->priv->cancellable);
	g_clear_object (&monitor->priv->cancellable);
160

161 162 163
	if (monitor->priv->name_watch_id) {
		g_bus_unwatch_name (monitor->priv->name_watch_id);
		monitor->priv->name_watch_id = 0;
164 165
	}

166
	g_clear_object (&monitor->priv->proxy);
167
	g_clear_object (&monitor->priv->om);
168
	g_clear_pointer (&monitor->priv->watches, g_hash_table_destroy);
169
	g_clear_pointer (&monitor->priv->watches_by_upstream_id, g_hash_table_destroy);
170
	g_clear_object (&monitor->priv->device);
171
	g_clear_pointer (&monitor->priv->path, g_free);
172

173
	G_OBJECT_CLASS (gnome_idle_monitor_parent_class)->dispose (object);
174 175
}

176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
static void
gnome_idle_monitor_get_property (GObject    *object,
				 guint       prop_id,
				 GValue     *value,
				 GParamSpec *pspec)
{
	GnomeIdleMonitor *monitor = GNOME_IDLE_MONITOR (object);
	switch (prop_id)
	{
	case PROP_DEVICE:
		g_value_set_object (value, monitor->priv->device);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
gnome_idle_monitor_set_property (GObject      *object,
				 guint         prop_id,
				 const GValue *value,
				 GParamSpec   *pspec)
{
	GnomeIdleMonitor *monitor = GNOME_IDLE_MONITOR (object);
	switch (prop_id)
	{
	case PROP_DEVICE:
		monitor->priv->device = g_value_dup_object (value);
205 206 207 208 209 210 211 212 213

		g_free (monitor->priv->path);
		if (monitor->priv->device) {
			monitor->priv->path = g_strdup_printf ("/org/gnome/Mutter/IdleMonitor/Device%d",
							       gdk_x11_device_get_id (monitor->priv->device));
		} else {
			monitor->priv->path = g_strdup ("/org/gnome/Mutter/IdleMonitor/Core");
		}

214 215 216 217 218 219 220 221
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
222 223 224
add_known_watch (gpointer key,
		 gpointer value,
		 gpointer user_data)
225
{
226 227
	GnomeIdleMonitor *monitor = user_data;
	GnomeIdleMonitorWatch *watch = value;
228

229 230 231 232 233 234 235
	if (watch->timeout_msec == 0)
		add_active_watch (monitor, watch);
	else
		add_idle_watch (monitor, watch);
}

static void
236 237
connect_proxy (GDBusObject	*object,
	       GnomeIdleMonitor	*monitor)
238 239 240
{
	MetaDBusIdleMonitor *proxy;

241
	proxy = meta_dbus_object_get_idle_monitor (META_DBUS_OBJECT (object));
242
	if (!proxy) {
243 244
		g_critical ("Unable to get idle monitor from object at %s",
			    g_dbus_object_get_object_path (object));
245 246 247 248 249 250 251 252 253
		return;
	}

	monitor->priv->proxy = proxy;
	g_signal_connect_object (proxy, "watch-fired", G_CALLBACK (on_watch_fired), monitor, 0);
	g_hash_table_foreach (monitor->priv->watches, add_known_watch, monitor);
}

static void
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
on_object_added (GDBusObjectManager	*manager,
		 GDBusObject		*object,
		 gpointer		 user_data)
{
	GnomeIdleMonitor *monitor = user_data;

	if (!g_str_equal (monitor->priv->path, g_dbus_object_get_object_path (object)))
		return;

	connect_proxy (object, monitor);

	g_signal_handlers_disconnect_by_func (manager, on_object_added, user_data);
}

static void
get_proxy (GnomeIdleMonitor *monitor)
270
{
271 272 273 274 275 276 277 278
	GDBusObject *object;

	object = g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (monitor->priv->om),
						   monitor->priv->path);
	if (object) {
		connect_proxy (object, monitor);
		g_object_unref (object);
		return;
279 280
	}

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
	g_signal_connect_object (monitor->priv->om, "object-added",
				 G_CALLBACK (on_object_added), monitor, 0);
}

static void
on_object_manager_ready (GObject	*source,
			 GAsyncResult	*res,
			 gpointer	 user_data)
{
	GnomeIdleMonitor *monitor = user_data;
	GDBusObjectManager *om;
	GError *error = NULL;

	om = meta_dbus_object_manager_client_new_finish (res, &error);
	if (!om) {
		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
			g_warning ("Failed to acquire idle monitor object manager: %s", error->message);
		g_error_free (error);
		return;
	}
301

302 303
	monitor->priv->om = META_DBUS_OBJECT_MANAGER_CLIENT (om);
	get_proxy (monitor);
304 305 306 307 308 309 310 311 312 313
}

static void
on_name_appeared (GDBusConnection *connection,
		  const char      *name,
		  const char      *name_owner,
		  gpointer         user_data)
{
	GnomeIdleMonitor *monitor = user_data;

314 315 316 317 318 319 320
	meta_dbus_object_manager_client_new (connection,
					     G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
					     name_owner,
					     "/org/gnome/Mutter/IdleMonitor",
					     monitor->priv->cancellable,
					     on_object_manager_ready,
					     monitor);
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
}

static void
clear_watch (gpointer key,
	     gpointer value,
	     gpointer user_data)
{
	GnomeIdleMonitorWatch *watch = value;
	GnomeIdleMonitor *monitor = user_data;

	g_hash_table_remove (monitor->priv->watches_by_upstream_id, GINT_TO_POINTER (watch->upstream_id));
	watch->upstream_id = 0;
}

static void
on_name_vanished (GDBusConnection *connection,
		  const char      *name,
		  gpointer         user_data)
{
	GnomeIdleMonitor *monitor = user_data;

	g_hash_table_foreach (monitor->priv->watches, clear_watch, monitor);
	g_clear_object (&monitor->priv->proxy);
344
	g_clear_object (&monitor->priv->om);
345 346
}

347 348 349 350 351 352 353 354 355
static gboolean
gnome_idle_monitor_initable_init (GInitable     *initable,
				  GCancellable  *cancellable,
				  GError       **error)
{
	GnomeIdleMonitor *monitor;

	monitor = GNOME_IDLE_MONITOR (initable);

356 357 358 359 360 361
	monitor->priv->name_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
							 "org.gnome.Mutter.IdleMonitor",
							 G_BUS_NAME_WATCHER_FLAGS_NONE,
							 on_name_appeared,
							 on_name_vanished,
							 monitor, NULL);
362 363 364 365 366 367 368 369 370 371

	return TRUE;
}

static void
gnome_idle_monitor_initable_iface_init (GInitableIface *iface)
{
	iface->init = gnome_idle_monitor_initable_init;
}

372 373 374 375 376 377
static void
gnome_idle_monitor_class_init (GnomeIdleMonitorClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	object_class->dispose = gnome_idle_monitor_dispose;
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
	object_class->get_property = gnome_idle_monitor_get_property;
	object_class->set_property = gnome_idle_monitor_set_property;

	/**
	 * GnomeIdleMonitor:device:
	 *
	 * The device to listen to idletime on.
	 */
	obj_props[PROP_DEVICE] =
		g_param_spec_object ("device",
				     "Device",
				     "The device to listen to idletime on",
				     GDK_TYPE_DEVICE,
				     G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
	g_object_class_install_property (object_class, PROP_DEVICE, obj_props[PROP_DEVICE]);
393 394 395 396 397 398 399 400 401 402 403 404

	g_type_class_add_private (klass, sizeof (GnomeIdleMonitorPrivate));
}

static void
gnome_idle_monitor_init (GnomeIdleMonitor *monitor)
{
	monitor->priv = GNOME_IDLE_MONITOR_GET_PRIVATE (monitor);

	monitor->priv->watches = g_hash_table_new_full (NULL,
							NULL,
							NULL,
405
							(GDestroyNotify)idle_monitor_watch_destroy);
406
	monitor->priv->watches_by_upstream_id = g_hash_table_new (NULL, NULL);
407

408
	monitor->priv->cancellable = g_cancellable_new ();
409 410
}

411 412 413 414 415 416 417
/**
 * gnome_idle_monitor_new:
 *
 * Returns: a new #GnomeIdleMonitor that tracks the server-global
 * idletime for all devices. To track device-specific idletime,
 * use gnome_idle_monitor_new_for_device().
 */
418 419 420
GnomeIdleMonitor *
gnome_idle_monitor_new (void)
{
421
	return GNOME_IDLE_MONITOR (g_initable_new (GNOME_TYPE_IDLE_MONITOR, NULL, NULL, NULL));
422 423
}

424 425 426
/**
 * gnome_idle_monitor_new_for_device:
 * @device: A #GdkDevice to get the idle time for.
427
 * @error: A pointer to a #GError or %NULL.
428 429
 *
 * Returns: a new #GnomeIdleMonitor that tracks the device-specific
430 431 432
 * idletime for @device. If device-specific idletime is not available,
 * %NULL is returned, and @error is set. To track server-global
 * idletime for all devices, use gnome_idle_monitor_new().
433 434
 */
GnomeIdleMonitor *
435 436
gnome_idle_monitor_new_for_device (GdkDevice  *device,
				   GError    **error)
437
{
438 439
	return GNOME_IDLE_MONITOR (g_initable_new (GNOME_TYPE_IDLE_MONITOR, NULL, error,
						   "device", device, NULL));
440 441
}

442 443
static GnomeIdleMonitorWatch *
make_watch (GnomeIdleMonitor          *monitor,
444
	    guint64                    timeout_msec,
445 446 447 448 449 450 451
	    GnomeIdleMonitorWatchFunc  callback,
	    gpointer                   user_data,
	    GDestroyNotify             notify)
{
	GnomeIdleMonitorWatch *watch;

	watch = g_slice_new0 (GnomeIdleMonitorWatch);
452
	watch->ref_count = 1;
453
	watch->id = get_next_watch_serial ();
454
	watch->monitor = monitor;
455 456 457
	watch->callback = callback;
	watch->user_data = user_data;
	watch->notify = notify;
458
	watch->timeout_msec = timeout_msec;
459 460 461 462

	return watch;
}

463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
static void
on_watch_added (GObject      *object,
		GAsyncResult *result,
		gpointer      user_data)
{
	GnomeIdleMonitorWatch *watch = user_data;
	GnomeIdleMonitor *monitor;
	GError *error;
	GVariant *res;

	error = NULL;
	res = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), result, &error);
	if (!res) {
		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
			g_error_free (error);
478
			idle_monitor_watch_unref (watch);
479 480 481 482 483
			return;
		}

		g_warning ("Failed to acquire idle monitor proxy: %s", error->message);
		g_error_free (error);
484 485 486 487 488 489
		idle_monitor_watch_unref (watch);
		return;
	}

	if (watch->dead) {
		idle_monitor_watch_unref (watch);
490 491 492 493 494 495 496 497 498
		return;
	}

	monitor = watch->monitor;
	g_variant_get (res, "(u)", &watch->upstream_id);
	g_variant_unref (res);

	g_hash_table_insert (monitor->priv->watches_by_upstream_id,
			     GINT_TO_POINTER (watch->upstream_id), watch);
499
	idle_monitor_watch_unref (watch);
500 501 502 503 504 505 506 507 508
}

static void
add_idle_watch (GnomeIdleMonitor      *monitor,
		GnomeIdleMonitorWatch *watch)
{
	meta_dbus_idle_monitor_call_add_idle_watch (monitor->priv->proxy,
						    watch->timeout_msec,
						    monitor->priv->cancellable,
509
						    on_watch_added, idle_monitor_watch_ref (watch));
510 511 512 513 514 515 516 517
}

static void
add_active_watch (GnomeIdleMonitor      *monitor,
		  GnomeIdleMonitorWatch *watch)
{
	meta_dbus_idle_monitor_call_add_user_active_watch (monitor->priv->proxy,
							   monitor->priv->cancellable,
518
							   on_watch_added, idle_monitor_watch_ref (watch));
519 520
}

521
/**
522
 * gnome_idle_monitor_add_idle_watch:
523
 * @monitor: A #GnomeIdleMonitor
524
 * @interval_msec: The idletime interval, in milliseconds
525
 * @callback: (allow-none): The callback to call when the user has
526
 *     accumulated @interval_msec milliseconds of idle time.
527 528
 * @user_data: (allow-none): The user data to pass to the callback
 * @notify: A #GDestroyNotify
529 530
 *
 * Returns: a watch id
531 532
 *
 * Adds a watch for a specific idle time. The callback will be called
533
 * when the user has accumulated @interval_msec milliseconds of idle time.
534 535
 * This function will return an ID that can either be passed to
 * gnome_idle_monitor_remove_watch(), or can be used to tell idle time
536 537 538 539
 * watches apart if you have more than one.
 *
 * Also note that this function will only care about positive transitions
 * (user's idle time exceeding a certain time). If you want to know about
540 541 542 543 544 545 546 547 548 549 550 551 552 553
 * when the user has become active, use
 * gnome_idle_monitor_add_user_active_watch().
 */
guint
gnome_idle_monitor_add_idle_watch (GnomeIdleMonitor	       *monitor,
				   guint64	                interval_msec,
				   GnomeIdleMonitorWatchFunc    callback,
				   gpointer			user_data,
				   GDestroyNotify		notify)
{
	GnomeIdleMonitorWatch *watch;

	g_return_val_if_fail (GNOME_IS_IDLE_MONITOR (monitor), 0);

554
	watch = make_watch (monitor,
555
			    interval_msec,
556 557 558
			    callback,
			    user_data,
			    notify);
559

560 561 562 563 564 565
	g_hash_table_insert (monitor->priv->watches,
			     GUINT_TO_POINTER (watch->id),
			     watch);

	if (monitor->priv->proxy)
		add_idle_watch (monitor, watch);
566

567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584
	return watch->id;
}

/**
 * gnome_idle_monitor_add_user_active_watch:
 * @monitor: A #GnomeIdleMonitor
 * @callback: (allow-none): The callback to call when the user is
 *     active again.
 * @user_data: (allow-none): The user data to pass to the callback
 * @notify: A #GDestroyNotify
 *
 * Returns: a watch id
 *
 * Add a one-time watch to know when the user is active again.
 * Note that this watch is one-time and will de-activate after the
 * function is called, for efficiency purposes. It's most convenient
 * to call this when an idle watch, as added by
 * gnome_idle_monitor_add_idle_watch(), has triggered.
585
 */
586
guint
587 588 589 590
gnome_idle_monitor_add_user_active_watch (GnomeIdleMonitor          *monitor,
					  GnomeIdleMonitorWatchFunc  callback,
					  gpointer		     user_data,
					  GDestroyNotify	     notify)
591 592 593
{
	GnomeIdleMonitorWatch *watch;

594
	g_return_val_if_fail (GNOME_IS_IDLE_MONITOR (monitor), 0);
595

596
	watch = make_watch (monitor,
597
			    0,
598 599 600
			    callback,
			    user_data,
			    notify);
601

602 603 604 605 606 607 608
	g_hash_table_insert (monitor->priv->watches,
			     GUINT_TO_POINTER (watch->id),
			     watch);

	if (monitor->priv->proxy)
		add_active_watch (monitor, watch);

609
	return watch->id;
610 611 612 613 614 615 616 617
}

/**
 * gnome_idle_monitor_remove_watch:
 * @monitor: A #GnomeIdleMonitor
 * @id: A watch ID
 *
 * Removes an idle time watcher, previously added by
618 619
 * gnome_idle_monitor_add_idle_watch() or
 * gnome_idle_monitor_add_user_active_watch().
620 621 622 623 624
 */
void
gnome_idle_monitor_remove_watch (GnomeIdleMonitor *monitor,
				 guint		   id)
{
625 626
	GnomeIdleMonitorWatch *watch;

627 628
	g_return_if_fail (GNOME_IS_IDLE_MONITOR (monitor));

629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644
	watch = g_hash_table_lookup (monitor->priv->watches, GINT_TO_POINTER (id));
	if (!watch)
		return;

	if (watch->upstream_id)
		meta_dbus_idle_monitor_call_remove_watch (monitor->priv->proxy,
							  watch->upstream_id,
							  NULL, NULL, NULL);

	gnome_idle_monitor_remove_watch_internal (monitor, id);
}

static void
gnome_idle_monitor_remove_watch_internal (GnomeIdleMonitor *monitor,
					  guint             id)
{
645 646 647 648 649 650 651 652
	g_hash_table_remove (monitor->priv->watches,
			     GUINT_TO_POINTER (id));
}

/**
 * gnome_idle_monitor_get_idletime:
 * @monitor: A #GnomeIdleMonitor
 *
653
 * Returns: The current idle time, in milliseconds
654
 */
655
guint64
656 657
gnome_idle_monitor_get_idletime (GnomeIdleMonitor *monitor)
{
658
	guint64 value;
659

660 661 662 663
	value = 0;
	if (monitor->priv->proxy)
		meta_dbus_idle_monitor_call_get_idletime_sync (monitor->priv->proxy, &value,
							       NULL, NULL);
664

665
	return value;
666
}