Blame libgnome-desktop/gnome-datetime-source.c

rpm-build 18b009
/* -*- mode: C; c-file-style: "linux"; indent-tabs-mode: t -*-
rpm-build 18b009
 * gdatetime-source.c - copy&paste from https://bugzilla.gnome.org/show_bug.cgi?id=655129
rpm-build 18b009
 *
rpm-build 18b009
 * Copyright (C) 2011 Red Hat, Inc.
rpm-build 18b009
 *
rpm-build 18b009
 * This program is free software; you can redistribute it and/or
rpm-build 18b009
 * modify it under the terms of the GNU Library General Public License
rpm-build 18b009
 * as published by the Free Software Foundation; either version 2 of
rpm-build 18b009
 * the License, or (at your option) any later version.
rpm-build 18b009
 *
rpm-build 18b009
 * This program is distributed in the hope that it will be useful, but
rpm-build 18b009
 * WITHOUT ANY WARRANTY; without even the implied warranty of
rpm-build 18b009
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
rpm-build 18b009
 * Library General Public License for more details.
rpm-build 18b009
 *
rpm-build 18b009
 * You should have received a copy of the GNU Library General Public
rpm-build 18b009
 * License along with this program; if not, write to the Free Software
rpm-build 18b009
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
rpm-build 18b009
 * 02110-1301, USA.
rpm-build 18b009
 *
rpm-build 18b009
 * Author: Colin Walters <walters@verbum.org>
rpm-build 18b009
 */
rpm-build 18b009
rpm-build 18b009
#include "config.h"
rpm-build 18b009
rpm-build 18b009
#define GNOME_DESKTOP_USE_UNSTABLE_API
rpm-build 18b009
#include "gnome-datetime-source.h"
rpm-build 18b009
rpm-build 18b009
#ifdef HAVE_TIMERFD
rpm-build 18b009
#include <sys/timerfd.h>
rpm-build 18b009
#include <unistd.h>
rpm-build 18b009
#include <string.h>
rpm-build 18b009
#endif
rpm-build 18b009
rpm-build 18b009
typedef struct _GDateTimeSource GDateTimeSource;
rpm-build 18b009
struct _GDateTimeSource
rpm-build 18b009
{
rpm-build 18b009
	GSource     source;
rpm-build 18b009
  
rpm-build 18b009
	gint64      real_expiration;
rpm-build 18b009
	gint64      wakeup_expiration;
rpm-build 18b009
rpm-build 18b009
	gboolean    cancel_on_set : 1;
rpm-build 18b009
	gboolean    initially_expired : 1;
rpm-build 18b009
rpm-build 18b009
	GPollFD     pollfd;
rpm-build 18b009
};
rpm-build 18b009
rpm-build 18b009
static inline void
rpm-build 18b009
g_datetime_source_reschedule (GDateTimeSource *datetime_source,
rpm-build 18b009
			      gint64                from_monotonic)
rpm-build 18b009
{
rpm-build 18b009
	datetime_source->wakeup_expiration = from_monotonic + G_TIME_SPAN_SECOND;
rpm-build 18b009
}
rpm-build 18b009
rpm-build 18b009
static gboolean
rpm-build 18b009
g_datetime_source_is_expired (GDateTimeSource *datetime_source)
rpm-build 18b009
{
rpm-build 18b009
	gint64 real_now;
rpm-build 18b009
	gint64 monotonic_now;
rpm-build 18b009
rpm-build 18b009
	real_now = g_get_real_time ();
rpm-build 18b009
	monotonic_now = g_source_get_time ((GSource*)datetime_source);
rpm-build 18b009
rpm-build 18b009
	if (datetime_source->initially_expired)
rpm-build 18b009
		return TRUE;
rpm-build 18b009
  
rpm-build 18b009
	if (datetime_source->real_expiration <= real_now)
rpm-build 18b009
		return TRUE;
rpm-build 18b009
rpm-build 18b009
	/* We can't really detect without system support when things
rpm-build 18b009
	 * change; so just trigger every second (i.e. our wakeup
rpm-build 18b009
	 * expiration)
rpm-build 18b009
	 */
rpm-build 18b009
	if (datetime_source->cancel_on_set && monotonic_now >= datetime_source->wakeup_expiration)
rpm-build 18b009
		return TRUE;
rpm-build 18b009
rpm-build 18b009
	return FALSE;
rpm-build 18b009
}
rpm-build 18b009
rpm-build 18b009
/* In prepare, we're just checking the monotonic time against
rpm-build 18b009
 * our projected wakeup.
rpm-build 18b009
 */
rpm-build 18b009
static gboolean
rpm-build 18b009
g_datetime_source_prepare (GSource *source,
rpm-build 18b009
			   gint    *timeout)
rpm-build 18b009
{
rpm-build 18b009
	GDateTimeSource *datetime_source = (GDateTimeSource*)source;
rpm-build 18b009
	gint64 monotonic_now;
rpm-build 18b009
rpm-build 18b009
#ifdef HAVE_TIMERFD
rpm-build 18b009
	if (datetime_source->pollfd.fd != -1) {
rpm-build 18b009
		*timeout = -1;
rpm-build 18b009
		return datetime_source->initially_expired;  /* Should be TRUE at most one time, FALSE forever after */
rpm-build 18b009
	}
rpm-build 18b009
#endif
rpm-build 18b009
rpm-build 18b009
	monotonic_now = g_source_get_time (source);
rpm-build 18b009
rpm-build 18b009
	if (monotonic_now < datetime_source->wakeup_expiration) {
rpm-build 18b009
		/* Round up to ensure that we don't try again too early */
rpm-build 18b009
		*timeout = (datetime_source->wakeup_expiration - monotonic_now + 999) / 1000;
rpm-build 18b009
		return FALSE;
rpm-build 18b009
	}
rpm-build 18b009
rpm-build 18b009
	*timeout = 0;
rpm-build 18b009
	return g_datetime_source_is_expired (datetime_source);
rpm-build 18b009
}
rpm-build 18b009
rpm-build 18b009
/* In check, we're looking at the wall clock.
rpm-build 18b009
 */
rpm-build 18b009
static gboolean 
rpm-build 18b009
g_datetime_source_check (GSource  *source)
rpm-build 18b009
{
rpm-build 18b009
	GDateTimeSource *datetime_source = (GDateTimeSource*)source;
rpm-build 18b009
rpm-build 18b009
#ifdef HAVE_TIMERFD
rpm-build 18b009
	if (datetime_source->pollfd.fd != -1)
rpm-build 18b009
		return datetime_source->pollfd.revents != 0;
rpm-build 18b009
#endif
rpm-build 18b009
rpm-build 18b009
	if (g_datetime_source_is_expired (datetime_source))
rpm-build 18b009
		return TRUE;
rpm-build 18b009
rpm-build 18b009
	g_datetime_source_reschedule (datetime_source, g_source_get_time (source));
rpm-build 18b009
  
rpm-build 18b009
	return FALSE;
rpm-build 18b009
}
rpm-build 18b009
rpm-build 18b009
static gboolean
rpm-build 18b009
g_datetime_source_dispatch (GSource    *source, 
rpm-build 18b009
			    GSourceFunc callback,
rpm-build 18b009
			    gpointer    user_data)
rpm-build 18b009
{
rpm-build 18b009
	GDateTimeSource *datetime_source = (GDateTimeSource*)source;
rpm-build 18b009
rpm-build 18b009
	datetime_source->initially_expired = FALSE;
rpm-build 18b009
rpm-build 18b009
	if (!callback) {
rpm-build 18b009
		g_warning ("Timeout source dispatched without callback\n"
rpm-build 18b009
			   "You must call g_source_set_callback().");
rpm-build 18b009
		return FALSE;
rpm-build 18b009
	}
rpm-build 18b009
  
rpm-build 18b009
	(callback) (user_data);
rpm-build 18b009
rpm-build 18b009
	/* Always false as this source is documented to run once */
rpm-build 18b009
	return FALSE;
rpm-build 18b009
}
rpm-build 18b009
rpm-build 18b009
static void
rpm-build 18b009
g_datetime_source_finalize (GSource *source)
rpm-build 18b009
{
rpm-build 18b009
#ifdef HAVE_TIMERFD
rpm-build 18b009
	GDateTimeSource *datetime_source = (GDateTimeSource*)source;
rpm-build 18b009
	if (datetime_source->pollfd.fd != -1)
rpm-build 18b009
		close (datetime_source->pollfd.fd);
rpm-build 18b009
#endif
rpm-build 18b009
}
rpm-build 18b009
rpm-build 18b009
static GSourceFuncs g_datetime_source_funcs = {
rpm-build 18b009
	g_datetime_source_prepare,
rpm-build 18b009
	g_datetime_source_check,
rpm-build 18b009
	g_datetime_source_dispatch,
rpm-build 18b009
	g_datetime_source_finalize
rpm-build 18b009
};
rpm-build 18b009
rpm-build 18b009
#ifdef HAVE_TIMERFD
rpm-build 18b009
static gboolean
rpm-build 18b009
g_datetime_source_init_timerfd (GDateTimeSource *datetime_source,
rpm-build 18b009
				gint64           expected_now_seconds,
rpm-build 18b009
				gint64           unix_seconds)
rpm-build 18b009
{
rpm-build 18b009
	struct itimerspec its;
rpm-build 18b009
	int settime_flags;
rpm-build 18b009
rpm-build 18b009
	datetime_source->pollfd.fd = timerfd_create (CLOCK_REALTIME, TFD_CLOEXEC);
rpm-build 18b009
	if (datetime_source->pollfd.fd == -1)
rpm-build 18b009
		return FALSE;
rpm-build 18b009
rpm-build 18b009
	memset (&its, 0, sizeof (its));
rpm-build 18b009
	its.it_value.tv_sec = (time_t) unix_seconds;
rpm-build 18b009
rpm-build 18b009
	/* http://article.gmane.org/gmane.linux.kernel/1132138 */
rpm-build 18b009
#ifndef TFD_TIMER_CANCEL_ON_SET
rpm-build 18b009
#define TFD_TIMER_CANCEL_ON_SET (1 << 1)
rpm-build 18b009
#endif
rpm-build 18b009
rpm-build 18b009
	settime_flags = TFD_TIMER_ABSTIME;
rpm-build 18b009
	if (datetime_source->cancel_on_set)
rpm-build 18b009
		settime_flags |= TFD_TIMER_CANCEL_ON_SET;
rpm-build 18b009
rpm-build 18b009
	if (timerfd_settime (datetime_source->pollfd.fd, settime_flags, &its, NULL) < 0) {
rpm-build 18b009
		close (datetime_source->pollfd.fd);
rpm-build 18b009
		datetime_source->pollfd.fd = -1;
rpm-build 18b009
		return FALSE;
rpm-build 18b009
	}
rpm-build 18b009
rpm-build 18b009
	/* Now we need to check that the clock didn't go backwards before we
rpm-build 18b009
	 * had the timerfd set up.  See
rpm-build 18b009
	 * https://bugzilla.gnome.org/show_bug.cgi?id=655129
rpm-build 18b009
	 */
rpm-build 18b009
	clock_gettime (CLOCK_REALTIME, &its.it_value);
rpm-build 18b009
	if (its.it_value.tv_sec < expected_now_seconds)
rpm-build 18b009
		datetime_source->initially_expired = TRUE;
rpm-build 18b009
rpm-build 18b009
	datetime_source->pollfd.events = G_IO_IN;
rpm-build 18b009
rpm-build 18b009
	g_source_add_poll ((GSource*) datetime_source, &datetime_source->pollfd);
rpm-build 18b009
rpm-build 18b009
	return TRUE;
rpm-build 18b009
}
rpm-build 18b009
#endif
rpm-build 18b009
rpm-build 18b009
/**
rpm-build 18b009
 * _gnome_date_time_source_new:
rpm-build 18b009
 * @now: The expected current time
rpm-build 18b009
 * @expiry: Time to await
rpm-build 18b009
 * @cancel_on_set: Also invoke callback if the system clock changes discontiguously
rpm-build 18b009
 *
rpm-build 18b009
 * This function is designed for programs that want to schedule an
rpm-build 18b009
 * event based on real (wall clock) time, as returned by
rpm-build 18b009
 * g_get_real_time().  For example, HOUR:MINUTE wall-clock displays
rpm-build 18b009
 * and calendaring software.  The callback will be invoked when the
rpm-build 18b009
 * specified wall clock time @expiry is reached.  This includes
rpm-build 18b009
 * events such as the system clock being set past the given time.
rpm-build 18b009
 *
rpm-build 18b009
 * Compare versus g_timeout_source_new() which is defined to use
rpm-build 18b009
 * monotonic time as returned by g_get_monotonic_time().
rpm-build 18b009
 *
rpm-build 18b009
 * The parameter @now is necessary to avoid a race condition in
rpm-build 18b009
 * between getting the current time and calling this function.
rpm-build 18b009
 *
rpm-build 18b009
 * If @cancel_on_set is given, the callback will also be invoked at
rpm-build 18b009
 * most a second after the system clock is changed.  This includes
rpm-build 18b009
 * being set backwards or forwards, and system
rpm-build 18b009
 * resume from suspend.  Not all operating systems allow detecting all
rpm-build 18b009
 * relevant events efficiently - this function may cause the process
rpm-build 18b009
 * to wake up once a second in those cases.
rpm-build 18b009
 *
rpm-build 18b009
 * A wall clock display should use @cancel_on_set; a calendaring
rpm-build 18b009
 * program shouldn't need to.
rpm-build 18b009
 *
rpm-build 18b009
 * Note that the return value from the associated callback will be
rpm-build 18b009
 * ignored; this is a one time watch.
rpm-build 18b009
 *
rpm-build 18b009
 * <note><para>This function currently does not detect time zone
rpm-build 18b009
 * changes.  On Linux, your program should also monitor the
rpm-build 18b009
 * <literal>/etc/timezone</literal> file using
rpm-build 18b009
 * #GFileMonitor.</para></note>
rpm-build 18b009
 *
rpm-build 18b009
 * <example id="gdatetime-example-watch"><title>Clock example</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../glib/tests/glib-clock.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
rpm-build 18b009
 *
rpm-build 18b009
 * Return value: A newly-constructed #GSource
rpm-build 18b009
 *
rpm-build 18b009
 * Since: 2.30
rpm-build 18b009
 **/
rpm-build 18b009
GSource *
rpm-build 18b009
_gnome_datetime_source_new (GDateTime  *now,
rpm-build 18b009
			    GDateTime  *expiry,
rpm-build 18b009
			    gboolean    cancel_on_set)
rpm-build 18b009
{
rpm-build 18b009
	GDateTimeSource *datetime_source;
rpm-build 18b009
	gint64 unix_seconds;
rpm-build 18b009
rpm-build 18b009
	unix_seconds = g_date_time_to_unix (expiry);
rpm-build 18b009
rpm-build 18b009
	datetime_source = (GDateTimeSource*) g_source_new (&g_datetime_source_funcs, sizeof (GDateTimeSource));
rpm-build 18b009
rpm-build 18b009
	datetime_source->cancel_on_set = cancel_on_set;
rpm-build 18b009
rpm-build 18b009
#ifdef HAVE_TIMERFD
rpm-build 18b009
	{
rpm-build 18b009
		gint64 expected_now_seconds = g_date_time_to_unix (now);
rpm-build 18b009
		if (g_datetime_source_init_timerfd (datetime_source, expected_now_seconds, unix_seconds))
rpm-build 18b009
			return (GSource*)datetime_source;
rpm-build 18b009
		/* Fall through to non-timerfd code */
rpm-build 18b009
	}
rpm-build 18b009
#endif
rpm-build 18b009
rpm-build 18b009
	datetime_source->real_expiration = unix_seconds * 1000000;
rpm-build 18b009
	g_datetime_source_reschedule (datetime_source, g_get_monotonic_time ());
rpm-build 18b009
rpm-build 18b009
	return (GSource*)datetime_source;
rpm-build 18b009
}
rpm-build 18b009