/* -*- 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 */ #include "config.h" #define GNOME_DESKTOP_USE_UNSTABLE_API #include "gnome-datetime-source.h" #ifdef HAVE_TIMERFD #include #include #include #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; #ifdef 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; #ifdef 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) { #ifdef 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 }; #ifdef 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. * * This function currently does not detect time zone * changes. On Linux, your program should also monitor the * /etc/timezone file using * #GFileMonitor. * * Clock exampleFIXME: MISSING XINCLUDE CONTENT * * 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; #ifdef 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; }