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