summaryrefslogtreecommitdiffstats
path: root/plugins/color
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:51:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:51:51 +0000
commitb0e30ceba2288eab10c6ff7be0ac0cb05a9ed0b7 (patch)
tree9f1d8a08a8cbd19d28ec2d31027f8a7ccd90de0d /plugins/color
parentInitial commit. (diff)
downloadgnome-settings-daemon-upstream.tar.xz
gnome-settings-daemon-upstream.zip
Adding upstream version 43.0.upstream/43.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugins/color')
-rw-r--r--plugins/color/gcm-self-test.c359
-rw-r--r--plugins/color/gnome-datetime-source.c286
-rw-r--r--plugins/color/gnome-datetime-source.h38
-rw-r--r--plugins/color/gsd-color-calibrate.c412
-rw-r--r--plugins/color/gsd-color-calibrate.h38
-rw-r--r--plugins/color/gsd-color-manager.c491
-rw-r--r--plugins/color/gsd-color-manager.h46
-rw-r--r--plugins/color/gsd-color-state.c82
-rw-r--r--plugins/color/gsd-color-state.h45
-rw-r--r--plugins/color/gsd-night-light-common.c137
-rw-r--r--plugins/color/gsd-night-light-common.h39
-rw-r--r--plugins/color/gsd-night-light.c807
-rw-r--r--plugins/color/gsd-night-light.h58
-rw-r--r--plugins/color/main.c7
-rw-r--r--plugins/color/meson.build50
-rw-r--r--plugins/color/test-data/LG-L225W-External.binbin0 -> 128 bytes
-rw-r--r--plugins/color/test-data/Lenovo-T61-Internal.binbin0 -> 128 bytes
17 files changed, 2895 insertions, 0 deletions
diff --git a/plugins/color/gcm-self-test.c b/plugins/color/gcm-self-test.c
new file mode 100644
index 0000000..e293861
--- /dev/null
+++ b/plugins/color/gcm-self-test.c
@@ -0,0 +1,359 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007-2011 Richard Hughes <richard@hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib-object.h>
+#include <stdlib.h>
+
+#include "gsd-color-state.h"
+#include "gsd-night-light.h"
+#include "gsd-night-light-common.h"
+
+GMainLoop *mainloop;
+
+static void
+on_notify (GsdNightLight *nlight,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ guint *cnt = (guint *) user_data;
+ (*cnt)++;
+}
+
+static gboolean
+quit_mainloop (gpointer user_data)
+{
+ g_main_loop_quit (mainloop);
+
+ return FALSE;
+}
+
+static void
+gcm_test_night_light (void)
+{
+ gboolean ret;
+ guint active_cnt = 0;
+ guint disabled_until_tmw_cnt = 0;
+ guint sunrise_cnt = 0;
+ guint sunset_cnt = 0;
+ guint temperature_cnt = 0;
+ g_autoptr(GDateTime) datetime_override = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GsdNightLight) nlight = NULL;
+ g_autoptr(GSettings) settings = NULL;
+
+ nlight = gsd_night_light_new ();
+ g_assert (GSD_IS_NIGHT_LIGHT (nlight));
+ g_signal_connect (nlight, "notify::active",
+ G_CALLBACK (on_notify), &active_cnt);
+ g_signal_connect (nlight, "notify::sunset",
+ G_CALLBACK (on_notify), &sunset_cnt);
+ g_signal_connect (nlight, "notify::sunrise",
+ G_CALLBACK (on_notify), &sunrise_cnt);
+ g_signal_connect (nlight, "notify::temperature",
+ G_CALLBACK (on_notify), &temperature_cnt);
+ g_signal_connect (nlight, "notify::disabled-until-tmw",
+ G_CALLBACK (on_notify), &disabled_until_tmw_cnt);
+
+ /* hardcode a specific date and time */
+ datetime_override = g_date_time_new_utc (2017, 2, 8, 20, 0, 0);
+ gsd_night_light_set_date_time_now (nlight, datetime_override);
+
+ /* do not start geoclue */
+ gsd_night_light_set_geoclue_enabled (nlight, FALSE);
+
+ /* do not smooth the transition */
+ gsd_night_light_set_smooth_enabled (nlight, FALSE);
+
+ /* switch off */
+ settings = g_settings_new ("org.gnome.settings-daemon.plugins.color");
+ g_settings_set_boolean (settings, "night-light-enabled", FALSE);
+ g_settings_set_uint (settings, "night-light-temperature", 4000);
+
+ /* check default values */
+ g_assert (!gsd_night_light_get_active (nlight));
+ g_assert_cmpint ((gint) gsd_night_light_get_sunrise (nlight), ==, -1);
+ g_assert_cmpint ((gint) gsd_night_light_get_sunset (nlight), ==, -1);
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT);
+ g_assert (!gsd_night_light_get_disabled_until_tmw (nlight));
+
+ /* start module, disabled */
+ ret = gsd_night_light_start (nlight, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+ g_assert (!gsd_night_light_get_active (nlight));
+ g_assert_cmpint (active_cnt, ==, 0);
+ g_assert_cmpint (sunset_cnt, ==, 0);
+ g_assert_cmpint (sunrise_cnt, ==, 0);
+ g_assert_cmpint (temperature_cnt, ==, 0);
+ g_assert_cmpint (disabled_until_tmw_cnt, ==, 0);
+
+ /* enable automatic mode */
+ g_settings_set_value (settings, "night-light-last-coordinates",
+ g_variant_new ("(dd)", 51.5, -0.1278));
+ g_settings_set_boolean (settings, "night-light-schedule-automatic", TRUE);
+ g_settings_set_boolean (settings, "night-light-enabled", TRUE);
+ g_assert (gsd_night_light_get_active (nlight));
+ g_assert_cmpint (active_cnt, ==, 1);
+ g_assert_cmpint (sunset_cnt, ==, 1);
+ g_assert_cmpint (sunrise_cnt, ==, 1);
+ g_assert_cmpint (temperature_cnt, ==, 1);
+ g_assert_cmpint (disabled_until_tmw_cnt, ==, 0);
+ g_assert_cmpint ((gint) gsd_night_light_get_sunrise (nlight), ==, 7);
+ g_assert_cmpint ((gint) gsd_night_light_get_sunset (nlight), ==, 17);
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, 4000);
+ g_assert (!gsd_night_light_get_disabled_until_tmw (nlight));
+
+ /* disable for one day */
+ gsd_night_light_set_disabled_until_tmw (nlight, TRUE);
+ gsd_night_light_set_disabled_until_tmw (nlight, TRUE);
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT);
+ g_assert (gsd_night_light_get_active (nlight));
+ g_assert (gsd_night_light_get_disabled_until_tmw (nlight));
+ g_assert_cmpint (temperature_cnt, ==, 2);
+ g_assert_cmpint (disabled_until_tmw_cnt, ==, 1);
+
+ /* change our mind */
+ gsd_night_light_set_disabled_until_tmw (nlight, FALSE);
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, 4000);
+ g_assert (gsd_night_light_get_active (nlight));
+ g_assert (!gsd_night_light_get_disabled_until_tmw (nlight));
+ g_assert_cmpint (active_cnt, ==, 1);
+ g_assert_cmpint (temperature_cnt, ==, 3);
+ g_assert_cmpint (disabled_until_tmw_cnt, ==, 2);
+
+ /* enabled manual mode (night shift) */
+ g_settings_set_double (settings, "night-light-schedule-from", 4.0);
+ g_settings_set_double (settings, "night-light-schedule-to", 16.f);
+ g_settings_set_boolean (settings, "night-light-schedule-automatic", FALSE);
+ g_assert_cmpint (active_cnt, ==, 2);
+ g_assert_cmpint (sunset_cnt, ==, 1);
+ g_assert_cmpint (sunrise_cnt, ==, 1);
+ g_assert_cmpint (temperature_cnt, ==, 4);
+ g_assert_cmpint (disabled_until_tmw_cnt, ==, 2);
+ g_assert (!gsd_night_light_get_active (nlight));
+ g_assert_cmpint ((gint) gsd_night_light_get_sunrise (nlight), ==, 7);
+ g_assert_cmpint ((gint) gsd_night_light_get_sunset (nlight), ==, 17);
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT);
+ g_assert (!gsd_night_light_get_disabled_until_tmw (nlight));
+
+ /* disable, with no changes */
+ g_settings_set_boolean (settings, "night-light-enabled", FALSE);
+ g_assert (!gsd_night_light_get_active (nlight));
+ g_assert_cmpint (active_cnt, ==, 2);
+ g_assert_cmpint (sunset_cnt, ==, 1);
+ g_assert_cmpint (sunrise_cnt, ==, 1);
+ g_assert_cmpint (temperature_cnt, ==, 4);
+ g_assert_cmpint (disabled_until_tmw_cnt, ==, 2);
+
+
+ /* Finally, check that cancelling a smooth transition works */
+ gsd_night_light_set_smooth_enabled (nlight, TRUE);
+ /* Enable night light and automatic scheduling */
+ g_settings_set_boolean (settings, "night-light-schedule-automatic", TRUE);
+ g_settings_set_boolean (settings, "night-light-enabled", TRUE);
+ /* It should be active again, and a smooth transition is being done,
+ * so the color temperature is still the default at this point. */
+ g_assert (gsd_night_light_get_active (nlight));
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT);
+
+ /* Turn off immediately, before the first timeout event is fired. */
+ g_settings_set_boolean (settings, "night-light-schedule-automatic", FALSE);
+ g_settings_set_boolean (settings, "night-light-enabled", FALSE);
+ g_assert (!gsd_night_light_get_active (nlight));
+
+ /* Now, sleep for a bit (the smooth transition time is 5 seconds) */
+ g_timeout_add (5000, quit_mainloop, NULL);
+ g_main_loop_run (mainloop);
+
+ /* Ensure that the color temperature is still the default one.*/
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT);
+
+
+ /* Check that disabled until tomorrow resets again correctly. */
+ g_settings_set_double (settings, "night-light-schedule-from", 17.0);
+ g_settings_set_double (settings, "night-light-schedule-to", 7.f);
+ g_settings_set_boolean (settings, "night-light-enabled", TRUE);
+ gsd_night_light_set_disabled_until_tmw (nlight, TRUE);
+
+ /* Move time past midnight */
+ g_clear_pointer (&datetime_override, g_date_time_unref);
+ datetime_override = g_date_time_new_utc (2017, 2, 9, 1, 0, 0);
+ gsd_night_light_set_date_time_now (nlight, datetime_override);
+ g_assert_true (gsd_night_light_get_disabled_until_tmw (nlight));
+
+ /* Move past sunrise */
+ g_clear_pointer (&datetime_override, g_date_time_unref);
+ datetime_override = g_date_time_new_utc (2017, 2, 9, 8, 0, 0);
+ gsd_night_light_set_date_time_now (nlight, datetime_override);
+ g_assert_false (gsd_night_light_get_disabled_until_tmw (nlight));
+
+ gsd_night_light_set_disabled_until_tmw (nlight, TRUE);
+
+ /* Move into night more than 24h in the future */
+ g_clear_pointer (&datetime_override, g_date_time_unref);
+ datetime_override = g_date_time_new_utc (2017, 2, 10, 20, 0, 0);
+ gsd_night_light_set_date_time_now (nlight, datetime_override);
+ g_assert_false (gsd_night_light_get_disabled_until_tmw (nlight));
+
+
+ /* Check that we are always in night mode if from/to are equal. */
+ gsd_night_light_set_smooth_enabled (nlight, FALSE);
+ g_settings_set_double (settings, "night-light-schedule-from", 6.0);
+ g_settings_set_double (settings, "night-light-schedule-to", 6.0);
+ g_settings_set_boolean (settings, "night-light-enabled", TRUE);
+
+ datetime_override = g_date_time_new_utc (2017, 2, 10, 5, 50, 0);
+ gsd_night_light_set_date_time_now (nlight, datetime_override);
+ g_assert (gsd_night_light_get_active (nlight));
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, 4000);
+
+ datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 0, 0);
+ gsd_night_light_set_date_time_now (nlight, datetime_override);
+ g_assert (gsd_night_light_get_active (nlight));
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, 4000);
+
+ datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 10, 0);
+ gsd_night_light_set_date_time_now (nlight, datetime_override);
+ g_assert (gsd_night_light_get_active (nlight));
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, 4000);
+
+
+ /* Check that the smearing time is lowered correctly when the times are close. */
+ g_settings_set_double (settings, "night-light-schedule-from", 6.0);
+ g_settings_set_double (settings, "night-light-schedule-to", 6.1);
+
+ /* Not enabled 10 minutes before sunset */
+ datetime_override = g_date_time_new_utc (2017, 2, 10, 5, 50, 0);
+ gsd_night_light_set_date_time_now (nlight, datetime_override);
+ g_assert_false (gsd_night_light_get_active (nlight));
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT);
+
+ /* Not enabled >10 minutes after sunrise */
+ datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 20, 0);
+ gsd_night_light_set_date_time_now (nlight, datetime_override);
+ g_assert_false (gsd_night_light_get_active (nlight));
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT);
+
+ /* ~50% smeared 3 min before sunrise (sunrise at 6 past) */
+ datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 3, 0);
+ gsd_night_light_set_date_time_now (nlight, datetime_override);
+ g_assert_true (gsd_night_light_get_active (nlight));
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), <=, (GSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 + 20);
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), >=, (GSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 - 20);
+
+ /* ~50% smeared 3 min before sunset (sunset at 6 past) */
+ g_settings_set_double (settings, "night-light-schedule-from", 6.1);
+ g_settings_set_double (settings, "night-light-schedule-to", 6.0);
+ datetime_override = g_date_time_new_utc (2017, 2, 10, 6, 3, 0);
+ gsd_night_light_set_date_time_now (nlight, datetime_override);
+ g_assert_true (gsd_night_light_get_active (nlight));
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), <=, (GSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 + 20);
+ g_assert_cmpint (gsd_night_light_get_temperature (nlight), >=, (GSD_COLOR_TEMPERATURE_DEFAULT + 4000) / 2 - 20);
+}
+
+static void
+gcm_test_sunset_sunrise (void)
+{
+ gdouble sunrise;
+ gdouble sunrise_actual = 7.6;
+ gdouble sunset;
+ gdouble sunset_actual = 16.8;
+ g_autoptr(GDateTime) dt = g_date_time_new_utc (2007, 2, 1, 0, 0, 0);
+
+ /* get for London, today */
+ gsd_night_light_get_sunrise_sunset (dt, 51.5, -0.1278, &sunrise, &sunset);
+ g_assert_cmpfloat (sunrise, <, sunrise_actual + 0.1);
+ g_assert_cmpfloat (sunrise, >, sunrise_actual - 0.1);
+ g_assert_cmpfloat (sunset, <, sunset_actual + 0.1);
+ g_assert_cmpfloat (sunset, >, sunset_actual - 0.1);
+}
+
+static void
+gcm_test_sunset_sunrise_fractional_timezone (void)
+{
+ gdouble sunrise;
+ gdouble sunrise_actual = 7.6 + 1.5;
+ gdouble sunset;
+ gdouble sunset_actual = 16.8 + 1.5;
+ g_autoptr(GTimeZone) tz = NULL;
+ g_autoptr(GDateTime) dt = NULL;
+
+ tz = g_time_zone_new ("+01:30");
+ dt = g_date_time_new (tz, 2007, 2, 1, 0, 0, 0);
+
+ /* get for our made up timezone, today */
+ gsd_night_light_get_sunrise_sunset (dt, 51.5, -0.1278, &sunrise, &sunset);
+ g_assert_cmpfloat (sunrise, <, sunrise_actual + 0.1);
+ g_assert_cmpfloat (sunrise, >, sunrise_actual - 0.1);
+ g_assert_cmpfloat (sunset, <, sunset_actual + 0.1);
+ g_assert_cmpfloat (sunset, >, sunset_actual - 0.1);
+}
+
+static void
+gcm_test_frac_day (void)
+{
+ g_autoptr(GDateTime) dt = g_date_time_new_utc (2007, 2, 1, 12, 59, 59);
+ gdouble fd;
+ gdouble fd_actual = 12.99;
+
+ /* test for 12:59:59 */
+ fd = gsd_night_light_frac_day_from_dt (dt);
+ g_assert_cmpfloat (fd, >, fd_actual - 0.01);
+ g_assert_cmpfloat (fd, <, fd_actual + 0.01);
+
+ /* test same day */
+ g_assert_true (gsd_night_light_frac_day_is_between (12, 6, 20));
+ g_assert_false (gsd_night_light_frac_day_is_between (5, 6, 20));
+ g_assert_true (gsd_night_light_frac_day_is_between (12, 0, 24));
+ g_assert_true (gsd_night_light_frac_day_is_between (12, -1, 25));
+
+ /* test rollover to next day */
+ g_assert_true (gsd_night_light_frac_day_is_between (23, 20, 6));
+ g_assert_false (gsd_night_light_frac_day_is_between (12, 20, 6));
+
+ /* test rollover to the previous day */
+ g_assert_true (gsd_night_light_frac_day_is_between (5, 16, 8));
+
+ /* test equality */
+ g_assert_true (gsd_night_light_frac_day_is_between (12, 0.5, 24.5));
+ g_assert_true (gsd_night_light_frac_day_is_between (0.5, 0.5, 0.5));
+}
+
+int
+main (int argc, char **argv)
+{
+ g_setenv ("GSETTINGS_BACKEND", "memory", TRUE);
+
+ g_test_init (&argc, &argv, NULL);
+
+ mainloop = g_main_loop_new (g_main_context_default (), FALSE);
+
+ g_test_add_func ("/color/sunset-sunrise", gcm_test_sunset_sunrise);
+ g_test_add_func ("/color/sunset-sunrise/fractional-timezone", gcm_test_sunset_sunrise_fractional_timezone);
+ g_test_add_func ("/color/fractional-day", gcm_test_frac_day);
+ g_test_add_func ("/color/night-light", gcm_test_night_light);
+
+ return g_test_run ();
+}
+
diff --git a/plugins/color/gnome-datetime-source.c b/plugins/color/gnome-datetime-source.c
new file mode 100644
index 0000000..287ba2d
--- /dev/null
+++ b/plugins/color/gnome-datetime-source.c
@@ -0,0 +1,286 @@
+/* -*- 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;
+}
+
diff --git a/plugins/color/gnome-datetime-source.h b/plugins/color/gnome-datetime-source.h
new file mode 100644
index 0000000..e9ecbf0
--- /dev/null
+++ b/plugins/color/gnome-datetime-source.h
@@ -0,0 +1,38 @@
+/* gnome-rr.h
+ *
+ * Copyright 2011, Red Hat, Inc.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library 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.
+ *
+ * The Gnome Library 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 the Gnome Library; see the file COPYING.LIB. 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>
+ */
+
+#ifndef GNOME_DATETIME_SOURCE_H
+#define GNOME_DATETIME_SOURCE_H
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+#error This is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API
+#endif
+
+#include <glib.h>
+
+GSource *_gnome_datetime_source_new (GDateTime *now,
+ GDateTime *expiry,
+ gboolean cancel_on_set);
+
+#endif /* GNOME_DATETIME_SOURCE_H */
diff --git a/plugins/color/gsd-color-calibrate.c b/plugins/color/gsd-color-calibrate.c
new file mode 100644
index 0000000..f642e18
--- /dev/null
+++ b/plugins/color/gsd-color-calibrate.c
@@ -0,0 +1,412 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard@hughsie.com>
+ *
+ * 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
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <colord.h>
+#include <libnotify/notify.h>
+#include <canberra-gtk.h>
+
+#include "gsd-color-calibrate.h"
+
+#define GCM_SESSION_NOTIFY_TIMEOUT 30000 /* ms */
+#define GCM_SETTINGS_RECALIBRATE_PRINTER_THRESHOLD "recalibrate-printer-threshold"
+#define GCM_SETTINGS_RECALIBRATE_DISPLAY_THRESHOLD "recalibrate-display-threshold"
+
+struct _GsdColorCalibrate
+{
+ GObject parent;
+
+ CdClient *client;
+ GSettings *settings;
+};
+
+static void gsd_color_calibrate_class_init (GsdColorCalibrateClass *klass);
+static void gsd_color_calibrate_init (GsdColorCalibrate *color_calibrate);
+static void gsd_color_calibrate_finalize (GObject *object);
+
+G_DEFINE_TYPE (GsdColorCalibrate, gsd_color_calibrate, G_TYPE_OBJECT)
+
+typedef struct {
+ GsdColorCalibrate *calibrate;
+ CdProfile *profile;
+ CdDevice *device;
+ guint32 output_id;
+} GcmSessionAsyncHelper;
+
+static void
+gcm_session_async_helper_free (GcmSessionAsyncHelper *helper)
+{
+ if (helper->calibrate != NULL)
+ g_object_unref (helper->calibrate);
+ if (helper->profile != NULL)
+ g_object_unref (helper->profile);
+ if (helper->device != NULL)
+ g_object_unref (helper->device);
+ g_free (helper);
+}
+
+static void
+gcm_session_exec_control_center (GsdColorCalibrate *calibrate)
+{
+ gboolean ret;
+ GError *error = NULL;
+ GAppInfo *app_info;
+ GdkAppLaunchContext *launch_context;
+
+ /* setup the launch context so the startup notification is correct */
+ launch_context = gdk_display_get_app_launch_context (gdk_display_get_default ());
+ app_info = g_app_info_create_from_commandline (BINDIR "/gnome-control-center color",
+ "gnome-control-center",
+ G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION,
+ &error);
+ if (app_info == NULL) {
+ g_warning ("failed to create application info: %s",
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* launch gnome-control-center */
+ ret = g_app_info_launch (app_info,
+ NULL,
+ G_APP_LAUNCH_CONTEXT (launch_context),
+ &error);
+ if (!ret) {
+ g_warning ("failed to launch gnome-control-center: %s",
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+out:
+ g_object_unref (launch_context);
+ if (app_info != NULL)
+ g_object_unref (app_info);
+}
+
+static void
+gcm_session_notify_cb (NotifyNotification *notification,
+ gchar *action,
+ gpointer user_data)
+{
+ GsdColorCalibrate *calibrate = GSD_COLOR_CALIBRATE (user_data);
+
+ if (g_strcmp0 (action, "recalibrate") == 0) {
+ notify_notification_close (notification, NULL);
+ gcm_session_exec_control_center (calibrate);
+ }
+}
+
+static void
+closed_cb (NotifyNotification *notification, gpointer data)
+{
+ g_object_unref (notification);
+}
+
+static gboolean
+gcm_session_notify_recalibrate (GsdColorCalibrate *calibrate,
+ const gchar *title,
+ const gchar *message,
+ CdDeviceKind kind)
+{
+ gboolean ret;
+ GError *error = NULL;
+ NotifyNotification *notification;
+
+ /* show a bubble */
+ notification = notify_notification_new (title, message, "preferences-color");
+ notify_notification_set_timeout (notification, GCM_SESSION_NOTIFY_TIMEOUT);
+ notify_notification_set_urgency (notification, NOTIFY_URGENCY_LOW);
+ notify_notification_set_app_name (notification, _("Color"));
+ notify_notification_set_hint_string (notification, "desktop-entry", "gnome-color-panel");
+
+ notify_notification_add_action (notification,
+ "recalibrate",
+ /* TRANSLATORS: button: this is to open GCM */
+ _("Recalibrate now"),
+ gcm_session_notify_cb,
+ calibrate, NULL);
+
+ g_signal_connect (notification, "closed", G_CALLBACK (closed_cb), NULL);
+ ret = notify_notification_show (notification, &error);
+ if (!ret) {
+ g_warning ("failed to show notification: %s",
+ error->message);
+ g_error_free (error);
+ }
+ return ret;
+}
+
+static gchar *
+gcm_session_device_get_title (CdDevice *device)
+{
+ const gchar *vendor;
+ const gchar *model;
+
+ model = cd_device_get_model (device);
+ vendor = cd_device_get_vendor (device);
+ if (model != NULL && vendor != NULL)
+ return g_strdup_printf ("%s - %s", vendor, model);
+ if (vendor != NULL)
+ return g_strdup (vendor);
+ if (model != NULL)
+ return g_strdup (model);
+ return g_strdup (cd_device_get_id (device));
+}
+
+static void
+gcm_session_notify_device (GsdColorCalibrate *calibrate, CdDevice *device)
+{
+ CdDeviceKind kind;
+ const gchar *title;
+ gchar *device_title = NULL;
+ gchar *message;
+ guint threshold;
+ glong since;
+
+ /* TRANSLATORS: this is when the device has not been recalibrated in a while */
+ title = _("Recalibration required");
+ device_title = gcm_session_device_get_title (device);
+
+ /* check we care */
+ kind = cd_device_get_kind (device);
+ if (kind == CD_DEVICE_KIND_DISPLAY) {
+
+ /* get from GSettings */
+ threshold = g_settings_get_uint (calibrate->settings,
+ GCM_SETTINGS_RECALIBRATE_DISPLAY_THRESHOLD);
+
+ /* TRANSLATORS: this is when the display has not been recalibrated in a while */
+ message = g_strdup_printf (_("The display “%s” should be recalibrated soon."),
+ device_title);
+ } else {
+
+ /* get from GSettings */
+ threshold = g_settings_get_uint (calibrate->settings,
+ GCM_SETTINGS_RECALIBRATE_PRINTER_THRESHOLD);
+
+ /* TRANSLATORS: this is when the printer has not been recalibrated in a while */
+ message = g_strdup_printf (_("The printer “%s” should be recalibrated soon."),
+ device_title);
+ }
+
+ /* check if we need to notify */
+ since = (g_get_real_time () - cd_device_get_modified (device)) / G_USEC_PER_SEC;
+ if (threshold > since)
+ gcm_session_notify_recalibrate (calibrate, title, message, kind);
+ g_free (device_title);
+ g_free (message);
+}
+
+static void
+gcm_session_profile_connect_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ const gchar *filename;
+ gboolean ret;
+ gchar *basename = NULL;
+ const gchar *data_source;
+ GError *error = NULL;
+ CdProfile *profile = CD_PROFILE (object);
+ GcmSessionAsyncHelper *helper = (GcmSessionAsyncHelper *) user_data;
+ GsdColorCalibrate *calibrate = GSD_COLOR_CALIBRATE (helper->calibrate);
+
+ ret = cd_profile_connect_finish (profile,
+ res,
+ &error);
+ if (!ret) {
+ g_warning ("failed to connect to profile: %s",
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* ensure it's a profile generated by us */
+ data_source = cd_profile_get_metadata_item (profile,
+ CD_PROFILE_METADATA_DATA_SOURCE);
+ if (data_source == NULL) {
+
+ /* existing profiles from gnome-color-calibrate < 3.1
+ * won't have the extra metadata values added */
+ filename = cd_profile_get_filename (profile);
+ if (filename == NULL)
+ goto out;
+ basename = g_path_get_basename (filename);
+ if (!g_str_has_prefix (basename, "GCM")) {
+ g_debug ("not a GCM profile for %s: %s",
+ cd_device_get_id (helper->device), filename);
+ goto out;
+ }
+
+ /* ensure it's been created from a calibration, rather than from
+ * auto-EDID */
+ } else if (g_strcmp0 (data_source,
+ CD_PROFILE_METADATA_DATA_SOURCE_CALIB) != 0) {
+ g_debug ("not a calib profile for %s",
+ cd_device_get_id (helper->device));
+ goto out;
+ }
+
+ /* handle device */
+ gcm_session_notify_device (calibrate, helper->device);
+out:
+ gcm_session_async_helper_free (helper);
+ g_free (basename);
+}
+
+static void
+gcm_session_device_connect_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ gboolean ret;
+ GError *error = NULL;
+ CdDeviceKind kind;
+ CdProfile *profile = NULL;
+ CdDevice *device = CD_DEVICE (object);
+ GsdColorCalibrate *calibrate = GSD_COLOR_CALIBRATE (user_data);
+ GcmSessionAsyncHelper *helper;
+
+ ret = cd_device_connect_finish (device,
+ res,
+ &error);
+ if (!ret) {
+ g_warning ("failed to connect to device: %s",
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* check we care */
+ kind = cd_device_get_kind (device);
+ if (kind != CD_DEVICE_KIND_DISPLAY &&
+ kind != CD_DEVICE_KIND_PRINTER)
+ goto out;
+
+ /* ensure we have a profile */
+ profile = cd_device_get_default_profile (device);
+ if (profile == NULL) {
+ g_debug ("no profile set for %s", cd_device_get_id (device));
+ goto out;
+ }
+
+ /* connect to the profile */
+ helper = g_new0 (GcmSessionAsyncHelper, 1);
+ helper->calibrate = g_object_ref (calibrate);
+ helper->device = g_object_ref (device);
+ cd_profile_connect (profile,
+ NULL,
+ gcm_session_profile_connect_cb,
+ helper);
+out:
+ if (profile != NULL)
+ g_object_unref (profile);
+}
+
+static void
+gcm_session_device_added_notify_cb (CdClient *client,
+ CdDevice *device,
+ GsdColorCalibrate *calibrate)
+{
+ /* connect to the device to get properties */
+ cd_device_connect (device,
+ NULL,
+ gcm_session_device_connect_cb,
+ calibrate);
+}
+
+static void
+gcm_session_sensor_added_cb (CdClient *client,
+ CdSensor *sensor,
+ GsdColorCalibrate *calibrate)
+{
+ ca_context_play (ca_gtk_context_get (), 0,
+ CA_PROP_EVENT_ID, "device-added",
+ /* TRANSLATORS: this is the application name */
+ CA_PROP_APPLICATION_NAME, _("GNOME Settings Daemon Color Plugin"),
+ /* TRANSLATORS: this is a sound description */
+ CA_PROP_EVENT_DESCRIPTION, _("Color calibration device added"), NULL);
+
+ /* open up the color prefs window */
+ gcm_session_exec_control_center (calibrate);
+}
+
+static void
+gcm_session_sensor_removed_cb (CdClient *client,
+ CdSensor *sensor,
+ GsdColorCalibrate *calibrate)
+{
+ ca_context_play (ca_gtk_context_get (), 0,
+ CA_PROP_EVENT_ID, "device-removed",
+ /* TRANSLATORS: this is the application name */
+ CA_PROP_APPLICATION_NAME, _("GNOME Settings Daemon Color Plugin"),
+ /* TRANSLATORS: this is a sound description */
+ CA_PROP_EVENT_DESCRIPTION, _("Color calibration device removed"), NULL);
+}
+
+static void
+gsd_color_calibrate_class_init (GsdColorCalibrateClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gsd_color_calibrate_finalize;
+}
+
+static void
+gsd_color_calibrate_init (GsdColorCalibrate *calibrate)
+{
+ calibrate->settings = g_settings_new ("org.gnome.settings-daemon.plugins.color");
+ calibrate->client = cd_client_new ();
+ g_signal_connect (calibrate->client, "device-added",
+ G_CALLBACK (gcm_session_device_added_notify_cb),
+ calibrate);
+ g_signal_connect (calibrate->client, "sensor-added",
+ G_CALLBACK (gcm_session_sensor_added_cb),
+ calibrate);
+ g_signal_connect (calibrate->client, "sensor-removed",
+ G_CALLBACK (gcm_session_sensor_removed_cb),
+ calibrate);
+}
+
+static void
+gsd_color_calibrate_finalize (GObject *object)
+{
+ GsdColorCalibrate *calibrate;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSD_IS_COLOR_CALIBRATE (object));
+
+ calibrate = GSD_COLOR_CALIBRATE (object);
+
+ g_clear_object (&calibrate->settings);
+ g_clear_object (&calibrate->client);
+
+ G_OBJECT_CLASS (gsd_color_calibrate_parent_class)->finalize (object);
+}
+
+GsdColorCalibrate *
+gsd_color_calibrate_new (void)
+{
+ GsdColorCalibrate *calibrate;
+ calibrate = g_object_new (GSD_TYPE_COLOR_CALIBRATE, NULL);
+ return GSD_COLOR_CALIBRATE (calibrate);
+}
diff --git a/plugins/color/gsd-color-calibrate.h b/plugins/color/gsd-color-calibrate.h
new file mode 100644
index 0000000..8980098
--- /dev/null
+++ b/plugins/color/gsd-color-calibrate.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard@hughsie.com>
+ *
+ * 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
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_COLOR_CALIBRATE_H
+#define __GSD_COLOR_CALIBRATE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_COLOR_CALIBRATE (gsd_color_calibrate_get_type ())
+G_DECLARE_FINAL_TYPE (GsdColorCalibrate, gsd_color_calibrate, GSD, COLOR_CALIBRATE, GObject)
+
+GType gsd_color_calibrate_get_type (void);
+GQuark gsd_color_calibrate_error_quark (void);
+
+GsdColorCalibrate * gsd_color_calibrate_new (void);
+
+G_END_DECLS
+
+#endif /* __GSD_COLOR_CALIBRATE_H */
diff --git a/plugins/color/gsd-color-manager.c b/plugins/color/gsd-color-manager.c
new file mode 100644
index 0000000..a9d8f74
--- /dev/null
+++ b/plugins/color/gsd-color-manager.c
@@ -0,0 +1,491 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard@hughsie.com>
+ *
+ * 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
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <math.h>
+
+#include "gnome-settings-profile.h"
+#include "gsd-color-calibrate.h"
+#include "gsd-color-manager.h"
+#include "gsd-color-state.h"
+#include "gsd-night-light.h"
+
+#define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
+#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
+#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon"
+
+#define GSD_COLOR_DBUS_NAME GSD_DBUS_NAME ".Color"
+#define GSD_COLOR_DBUS_PATH GSD_DBUS_PATH "/Color"
+#define GSD_COLOR_DBUS_INTERFACE GSD_DBUS_BASE_INTERFACE ".Color"
+
+static const gchar introspection_xml[] =
+"<node>"
+" <interface name='org.gnome.SettingsDaemon.Color'>"
+" <method name='NightLightPreview'>"
+" <arg type='u' name='duration' direction='in'/>"
+" </method>"
+" <property name='NightLightActive' type='b' access='read'/>"
+" <property name='Temperature' type='u' access='readwrite'/>"
+" <property name='DisabledUntilTomorrow' type='b' access='readwrite'/>"
+" <property name='Sunrise' type='d' access='read'/>"
+" <property name='Sunset' type='d' access='read'/>"
+" </interface>"
+"</node>";
+
+struct _GsdColorManager
+{
+ GObject parent;
+
+ /* D-Bus */
+ guint name_id;
+ GDBusNodeInfo *introspection_data;
+ GDBusConnection *connection;
+ GCancellable *bus_cancellable;
+
+ GsdColorCalibrate *calibrate;
+ GsdColorState *state;
+ GsdNightLight *nlight;
+
+ guint nlight_forced_timeout_id;
+};
+
+enum {
+ PROP_0,
+};
+
+static void gsd_color_manager_class_init (GsdColorManagerClass *klass);
+static void gsd_color_manager_init (GsdColorManager *color_manager);
+static void gsd_color_manager_finalize (GObject *object);
+
+G_DEFINE_TYPE (GsdColorManager, gsd_color_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+GQuark
+gsd_color_manager_error_quark (void)
+{
+ static GQuark quark = 0;
+ if (!quark)
+ quark = g_quark_from_static_string ("gsd_color_manager_error");
+ return quark;
+}
+
+gboolean
+gsd_color_manager_start (GsdColorManager *manager,
+ GError **error)
+{
+ g_debug ("Starting color manager");
+ gnome_settings_profile_start (NULL);
+
+ /* start the device probing */
+ gsd_color_state_start (manager->state);
+
+ gnome_settings_profile_end (NULL);
+ return TRUE;
+}
+
+void
+gsd_color_manager_stop (GsdColorManager *manager)
+{
+ g_debug ("Stopping color manager");
+ gsd_color_state_stop (manager->state);
+}
+
+static void
+gsd_color_manager_class_init (GsdColorManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gsd_color_manager_finalize;
+}
+
+static void
+emit_property_changed (GsdColorManager *manager,
+ const gchar *property_name,
+ GVariant *property_value)
+{
+ GVariantBuilder builder;
+ GVariantBuilder invalidated_builder;
+
+ /* not yet connected */
+ if (manager->connection == NULL)
+ return;
+
+ /* build the dict */
+ g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+ g_variant_builder_add (&builder,
+ "{sv}",
+ property_name,
+ property_value);
+ g_dbus_connection_emit_signal (manager->connection,
+ NULL,
+ GSD_COLOR_DBUS_PATH,
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ g_variant_new ("(sa{sv}as)",
+ GSD_COLOR_DBUS_INTERFACE,
+ &builder,
+ &invalidated_builder),
+ NULL);
+ g_variant_builder_clear (&builder);
+ g_variant_builder_clear (&invalidated_builder);
+}
+
+static void
+on_active_notify (GsdNightLight *nlight,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+ emit_property_changed (manager, "NightLightActive",
+ g_variant_new_boolean (gsd_night_light_get_active (manager->nlight)));
+}
+
+static void
+on_sunset_notify (GsdNightLight *nlight,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+ emit_property_changed (manager, "Sunset",
+ g_variant_new_double (gsd_night_light_get_sunset (manager->nlight)));
+}
+
+static void
+on_sunrise_notify (GsdNightLight *nlight,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+ emit_property_changed (manager, "Sunrise",
+ g_variant_new_double (gsd_night_light_get_sunrise (manager->nlight)));
+}
+
+static void
+on_disabled_until_tmw_notify (GsdNightLight *nlight,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+ emit_property_changed (manager, "DisabledUntilTomorrow",
+ g_variant_new_boolean (gsd_night_light_get_disabled_until_tmw (manager->nlight)));
+}
+
+static void
+on_temperature_notify (GsdNightLight *nlight,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+ gdouble temperature = gsd_night_light_get_temperature (manager->nlight);
+ gsd_color_state_set_temperature (manager->state, temperature);
+ emit_property_changed (manager, "Temperature",
+ g_variant_new_uint32 (roundf (temperature)));
+}
+
+static void
+gsd_color_manager_init (GsdColorManager *manager)
+{
+ /* setup calibration features */
+ manager->calibrate = gsd_color_calibrate_new ();
+ manager->state = gsd_color_state_new ();
+
+ /* night light features */
+ manager->nlight = gsd_night_light_new ();
+ g_signal_connect (manager->nlight, "notify::active",
+ G_CALLBACK (on_active_notify), manager);
+ g_signal_connect (manager->nlight, "notify::sunset",
+ G_CALLBACK (on_sunset_notify), manager);
+ g_signal_connect (manager->nlight, "notify::sunrise",
+ G_CALLBACK (on_sunrise_notify), manager);
+ g_signal_connect (manager->nlight, "notify::temperature",
+ G_CALLBACK (on_temperature_notify), manager);
+ g_signal_connect (manager->nlight, "notify::disabled-until-tmw",
+ G_CALLBACK (on_disabled_until_tmw_notify), manager);
+}
+
+static void
+gsd_color_manager_finalize (GObject *object)
+{
+ GsdColorManager *manager;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSD_IS_COLOR_MANAGER (object));
+
+ manager = GSD_COLOR_MANAGER (object);
+
+ gsd_color_manager_stop (manager);
+
+ if (manager->bus_cancellable != NULL) {
+ g_cancellable_cancel (manager->bus_cancellable);
+ g_clear_object (&manager->bus_cancellable);
+ }
+
+ g_clear_pointer (&manager->introspection_data, g_dbus_node_info_unref);
+ g_clear_object (&manager->connection);
+
+ if (manager->name_id != 0) {
+ g_bus_unown_name (manager->name_id);
+ manager->name_id = 0;
+ }
+
+ if (manager->nlight_forced_timeout_id)
+ g_source_remove (manager->nlight_forced_timeout_id);
+
+ g_clear_object (&manager->calibrate);
+ g_clear_object (&manager->state);
+ g_clear_object (&manager->nlight);
+
+ G_OBJECT_CLASS (gsd_color_manager_parent_class)->finalize (object);
+}
+
+static gboolean
+nlight_forced_timeout_cb (gpointer user_data)
+{
+ GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+
+ manager->nlight_forced_timeout_id = 0;
+ gsd_night_light_set_forced (manager->nlight, FALSE);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+handle_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+
+ if (g_strcmp0 (method_name, "NightLightPreview") == 0) {
+ guint32 duration = 0;
+
+ if (!manager->nlight) {
+ g_dbus_method_invocation_return_error_literal (invocation,
+ G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
+ "Night-light is currently unavailable");
+
+ return;
+ }
+
+ g_variant_get (parameters, "(u)", &duration);
+
+ if (duration == 0 || duration > 120) {
+ g_dbus_method_invocation_return_error_literal (invocation,
+ G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+ "Duration is out of the range (0-120].");
+
+ return;
+ }
+
+ if (manager->nlight_forced_timeout_id)
+ g_source_remove (manager->nlight_forced_timeout_id);
+ manager->nlight_forced_timeout_id = g_timeout_add_seconds (duration, nlight_forced_timeout_cb, manager);
+
+ gsd_night_light_set_forced (manager->nlight, TRUE);
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ } else {
+ g_assert_not_reached ();
+ }
+}
+
+static GVariant *
+handle_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error, gpointer user_data)
+{
+ GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+
+ if (g_strcmp0 (interface_name, GSD_COLOR_DBUS_INTERFACE) != 0) {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "No such interface: %s", interface_name);
+ return NULL;
+ }
+
+ if (g_strcmp0 (property_name, "NightLightActive") == 0)
+ return g_variant_new_boolean (gsd_night_light_get_active (manager->nlight));
+
+ if (g_strcmp0 (property_name, "Temperature") == 0) {
+ guint temperature;
+ temperature = gsd_color_state_get_temperature (manager->state);
+ return g_variant_new_uint32 (temperature);
+ }
+
+ if (g_strcmp0 (property_name, "DisabledUntilTomorrow") == 0)
+ return g_variant_new_boolean (gsd_night_light_get_disabled_until_tmw (manager->nlight));
+
+ if (g_strcmp0 (property_name, "Sunrise") == 0)
+ return g_variant_new_double (gsd_night_light_get_sunrise (manager->nlight));
+
+ if (g_strcmp0 (property_name, "Sunset") == 0)
+ return g_variant_new_double (gsd_night_light_get_sunset (manager->nlight));
+
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "Failed to get property: %s", property_name);
+ return NULL;
+}
+
+static gboolean
+handle_set_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GVariant *value,
+ GError **error, gpointer user_data)
+{
+ GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+
+ if (g_strcmp0 (interface_name, GSD_COLOR_DBUS_INTERFACE) != 0) {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "No such interface: %s", interface_name);
+ return FALSE;
+ }
+
+ if (g_strcmp0 (property_name, "Temperature") == 0) {
+ guint32 temperature;
+ g_variant_get (value, "u", &temperature);
+ if (temperature < GSD_COLOR_TEMPERATURE_MIN) {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "%" G_GUINT32_FORMAT "K is < min %" G_GUINT32_FORMAT "K",
+ temperature, GSD_COLOR_TEMPERATURE_MIN);
+ return FALSE;
+ }
+ if (temperature > GSD_COLOR_TEMPERATURE_MAX) {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "%" G_GUINT32_FORMAT "K is > max %" G_GUINT32_FORMAT "K",
+ temperature, GSD_COLOR_TEMPERATURE_MAX);
+ return FALSE;
+ }
+ gsd_color_state_set_temperature (manager->state, temperature);
+ return TRUE;
+ }
+
+ if (g_strcmp0 (property_name, "DisabledUntilTomorrow") == 0) {
+ gsd_night_light_set_disabled_until_tmw (manager->nlight,
+ g_variant_get_boolean (value));
+ return TRUE;
+ }
+
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "No such property: %s", property_name);
+ return FALSE;
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+ handle_method_call,
+ handle_get_property,
+ handle_set_property
+};
+
+static void
+name_lost_handler_cb (GDBusConnection *connection, const gchar *name, gpointer user_data)
+{
+ g_debug ("lost name, so exiting");
+ gtk_main_quit ();
+}
+
+static void
+on_bus_gotten (GObject *source_object,
+ GAsyncResult *res,
+ GsdColorManager *manager)
+{
+ GDBusConnection *connection;
+ GError *error = NULL;
+
+ connection = g_bus_get_finish (res, &error);
+ if (connection == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Could not get session bus: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ manager->connection = connection;
+
+ g_dbus_connection_register_object (connection,
+ GSD_COLOR_DBUS_PATH,
+ manager->introspection_data->interfaces[0],
+ &interface_vtable,
+ manager,
+ NULL,
+ NULL);
+
+ manager->name_id = g_bus_own_name_on_connection (connection,
+ GSD_COLOR_DBUS_NAME,
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ NULL,
+ name_lost_handler_cb,
+ manager,
+ NULL);
+
+ /* setup night light module */
+ if (!gsd_night_light_start (manager->nlight, &error)) {
+ g_warning ("Could not start night light module: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+register_manager_dbus (GsdColorManager *manager)
+{
+ manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+ g_assert (manager->introspection_data != NULL);
+ manager->bus_cancellable = g_cancellable_new ();
+
+ g_bus_get (G_BUS_TYPE_SESSION,
+ manager->bus_cancellable,
+ (GAsyncReadyCallback) on_bus_gotten,
+ manager);
+}
+
+GsdColorManager *
+gsd_color_manager_new (void)
+{
+ if (manager_object != NULL) {
+ g_object_ref (manager_object);
+ } else {
+ manager_object = g_object_new (GSD_TYPE_COLOR_MANAGER, NULL);
+ g_object_add_weak_pointer (manager_object,
+ (gpointer *) &manager_object);
+ register_manager_dbus (manager_object);
+ }
+
+ return GSD_COLOR_MANAGER (manager_object);
+}
diff --git a/plugins/color/gsd-color-manager.h b/plugins/color/gsd-color-manager.h
new file mode 100644
index 0000000..ba71fca
--- /dev/null
+++ b/plugins/color/gsd-color-manager.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011 Richard Hughes <richard@hughsie.com>
+ *
+ * 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
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_COLOR_MANAGER_H
+#define __GSD_COLOR_MANAGER_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_COLOR_MANAGER (gsd_color_manager_get_type ())
+#define GSD_COLOR_MANAGER_ERROR (gsd_color_manager_error_quark ())
+G_DECLARE_FINAL_TYPE (GsdColorManager, gsd_color_manager, GSD, COLOR_MANAGER, GObject)
+
+enum
+{
+ GSD_COLOR_MANAGER_ERROR_FAILED
+};
+
+GQuark gsd_color_manager_error_quark (void);
+
+GsdColorManager * gsd_color_manager_new (void);
+gboolean gsd_color_manager_start (GsdColorManager *manager,
+ GError **error);
+void gsd_color_manager_stop (GsdColorManager *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_COLOR_MANAGER_H */
diff --git a/plugins/color/gsd-color-state.c b/plugins/color/gsd-color-state.c
new file mode 100644
index 0000000..eb48eb7
--- /dev/null
+++ b/plugins/color/gsd-color-state.c
@@ -0,0 +1,82 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2020 NVIDIA CORPORATION
+ *
+ * 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
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include "gsd-color-manager.h"
+#include "gsd-color-state.h"
+
+struct _GsdColorState
+{
+ GObject parent;
+
+ guint color_temperature;
+};
+
+static void gsd_color_state_class_init (GsdColorStateClass *klass);
+static void gsd_color_state_init (GsdColorState *color_state);
+
+G_DEFINE_TYPE (GsdColorState, gsd_color_state, G_TYPE_OBJECT)
+
+void
+gsd_color_state_set_temperature (GsdColorState *state, guint temperature)
+{
+ g_return_if_fail (GSD_IS_COLOR_STATE (state));
+
+ state->color_temperature = temperature;
+}
+
+guint
+gsd_color_state_get_temperature (GsdColorState *state)
+{
+ g_return_val_if_fail (GSD_IS_COLOR_STATE (state), 0);
+ return state->color_temperature;
+}
+
+void
+gsd_color_state_start (GsdColorState *state)
+{
+}
+
+void
+gsd_color_state_stop (GsdColorState *state)
+{
+}
+
+static void
+gsd_color_state_class_init (GsdColorStateClass *klass)
+{
+}
+
+static void
+gsd_color_state_init (GsdColorState *state)
+{
+ /* default color temperature */
+ state->color_temperature = GSD_COLOR_TEMPERATURE_DEFAULT;
+}
+
+GsdColorState *
+gsd_color_state_new (void)
+{
+ GsdColorState *state;
+ state = g_object_new (GSD_TYPE_COLOR_STATE, NULL);
+ return GSD_COLOR_STATE (state);
+}
diff --git a/plugins/color/gsd-color-state.h b/plugins/color/gsd-color-state.h
new file mode 100644
index 0000000..c296e20
--- /dev/null
+++ b/plugins/color/gsd-color-state.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard@hughsie.com>
+ *
+ * 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
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_COLOR_STATE_H
+#define __GSD_COLOR_STATE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_COLOR_STATE (gsd_color_state_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdColorState, gsd_color_state, GSD, COLOR_STATE, GObject)
+
+#define GSD_COLOR_TEMPERATURE_MIN 1000 /* Kelvin */
+#define GSD_COLOR_TEMPERATURE_DEFAULT 6500 /* Kelvin, is RGB [1.0,1.0,1.0] */
+#define GSD_COLOR_TEMPERATURE_MAX 10000 /* Kelvin */
+
+GsdColorState * gsd_color_state_new (void);
+void gsd_color_state_start (GsdColorState *state);
+void gsd_color_state_stop (GsdColorState *state);
+void gsd_color_state_set_temperature (GsdColorState *state,
+ guint temperature);
+guint gsd_color_state_get_temperature (GsdColorState *state);
+
+G_END_DECLS
+
+#endif /* __GSD_COLOR_STATE_H */
diff --git a/plugins/color/gsd-night-light-common.c b/plugins/color/gsd-night-light-common.c
new file mode 100644
index 0000000..5fe756e
--- /dev/null
+++ b/plugins/color/gsd-night-light-common.c
@@ -0,0 +1,137 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
+ *
+ * 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
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <math.h>
+
+#include "gsd-night-light-common.h"
+
+static gdouble
+deg2rad (gdouble degrees)
+{
+ return (M_PI * degrees) / 180.f;
+}
+
+static gdouble
+rad2deg (gdouble radians)
+{
+ return radians * (180.f / M_PI);
+}
+
+/*
+ * Formulas taken from https://www.esrl.noaa.gov/gmd/grad/solcalc/calcdetails.html
+ *
+ * The returned values are fractional hours, so 6am would be 6.0 and 4:30pm
+ * would be 16.5.
+ *
+ * The values returned by this function might not make sense for locations near
+ * the polar regions. For example, in the north of Lapland there might not be
+ * a sunrise at all.
+ */
+gboolean
+gsd_night_light_get_sunrise_sunset (GDateTime *dt,
+ gdouble pos_lat, gdouble pos_long,
+ gdouble *sunrise, gdouble *sunset)
+{
+ g_autoptr(GDateTime) dt_zero = g_date_time_new_utc (1900, 1, 1, 0, 0, 0);
+ GTimeSpan ts = g_date_time_difference (dt, dt_zero);
+
+ g_return_val_if_fail (pos_lat <= 90.f && pos_lat >= -90.f, FALSE);
+ g_return_val_if_fail (pos_long <= 180.f && pos_long >= -180.f, FALSE);
+
+ gdouble tz_offset = (gdouble) g_date_time_get_utc_offset (dt) / G_USEC_PER_SEC / 60 / 60; // B5
+ gdouble date_as_number = ts / G_USEC_PER_SEC / 24 / 60 / 60 + 2; // B7
+ gdouble time_past_local_midnight = 0; // E2, unused in this calculation
+ gdouble julian_day = date_as_number + 2415018.5 +
+ time_past_local_midnight - tz_offset / 24;
+ gdouble julian_century = (julian_day - 2451545) / 36525;
+ gdouble geom_mean_long_sun = fmod (280.46646 + julian_century *
+ (36000.76983 + julian_century * 0.0003032), 360); // I2
+ gdouble geom_mean_anom_sun = 357.52911 + julian_century *
+ (35999.05029 - 0.0001537 * julian_century); // J2
+ gdouble eccent_earth_orbit = 0.016708634 - julian_century *
+ (0.000042037 + 0.0000001267 * julian_century); // K2
+ gdouble sun_eq_of_ctr = sin (deg2rad (geom_mean_anom_sun)) *
+ (1.914602 - julian_century * (0.004817 + 0.000014 * julian_century)) +
+ sin (deg2rad (2 * geom_mean_anom_sun)) * (0.019993 - 0.000101 * julian_century) +
+ sin (deg2rad (3 * geom_mean_anom_sun)) * 0.000289; // L2
+ gdouble sun_true_long = geom_mean_long_sun + sun_eq_of_ctr; // M2
+ gdouble sun_app_long = sun_true_long - 0.00569 - 0.00478 *
+ sin (deg2rad (125.04 - 1934.136 * julian_century)); // P2
+ gdouble mean_obliq_ecliptic = 23 + (26 + ((21.448 - julian_century *
+ (46.815 + julian_century * (0.00059 - julian_century * 0.001813)))) / 60) / 60; // Q2
+ gdouble obliq_corr = mean_obliq_ecliptic + 0.00256 *
+ cos (deg2rad (125.04 - 1934.136 * julian_century)); // R2
+ gdouble sun_declin = rad2deg (asin (sin (deg2rad (obliq_corr)) *
+ sin (deg2rad (sun_app_long)))); // T2
+ gdouble var_y = tan (deg2rad (obliq_corr/2)) * tan (deg2rad (obliq_corr / 2)); // U2
+ gdouble eq_of_time = 4 * rad2deg (var_y * sin (2 * deg2rad (geom_mean_long_sun)) -
+ 2 * eccent_earth_orbit * sin (deg2rad (geom_mean_anom_sun)) +
+ 4 * eccent_earth_orbit * var_y *
+ sin (deg2rad (geom_mean_anom_sun)) *
+ cos (2 * deg2rad (geom_mean_long_sun)) -
+ 0.5 * var_y * var_y * sin (4 * deg2rad (geom_mean_long_sun)) -
+ 1.25 * eccent_earth_orbit * eccent_earth_orbit *
+ sin (2 * deg2rad (geom_mean_anom_sun))); // V2
+ gdouble ha_sunrise = rad2deg (acos (cos (deg2rad (90.833)) / (cos (deg2rad (pos_lat)) *
+ cos (deg2rad (sun_declin))) - tan (deg2rad (pos_lat)) *
+ tan (deg2rad (sun_declin)))); // W2
+ gdouble solar_noon = (720 - 4 * pos_long - eq_of_time + tz_offset * 60) / 1440; // X2
+ gdouble sunrise_time = solar_noon - ha_sunrise * 4 / 1440; // Y2
+ gdouble sunset_time = solar_noon + ha_sunrise * 4 / 1440; // Z2
+
+ /* convert to hours */
+ if (sunrise != NULL)
+ *sunrise = sunrise_time * 24;
+ if (sunset != NULL)
+ *sunset = sunset_time * 24;
+ return TRUE;
+}
+
+gdouble
+gsd_night_light_frac_day_from_dt (GDateTime *dt)
+{
+ return g_date_time_get_hour (dt) +
+ (gdouble) g_date_time_get_minute (dt) / 60.f +
+ (gdouble) g_date_time_get_second (dt) / 3600.f;
+}
+
+gboolean
+gsd_night_light_frac_day_is_between (gdouble value,
+ gdouble start,
+ gdouble end)
+{
+ /* wrap end to the next day if it is before start,
+ * considering equal values as a full 24h period
+ */
+ if (end <= start)
+ end += 24;
+
+ /* wrap value to the next day if it is before the range */
+ if (value < start && value < end)
+ value += 24;
+
+ /* Check whether value falls into range; together with the 24h
+ * wrap around above this means that TRUE is always returned when
+ * start == end.
+ */
+ return value >= start && value < end;
+}
diff --git a/plugins/color/gsd-night-light-common.h b/plugins/color/gsd-night-light-common.h
new file mode 100644
index 0000000..4995da5
--- /dev/null
+++ b/plugins/color/gsd-night-light-common.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
+ *
+ * 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
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_NIGHT_LIGHT_COMMON_H
+#define __GSD_NIGHT_LIGHT_COMMON_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+gboolean gsd_night_light_get_sunrise_sunset (GDateTime *dt,
+ gdouble pos_lat,
+ gdouble pos_long,
+ gdouble *sunrise,
+ gdouble *sunset);
+gdouble gsd_night_light_frac_day_from_dt (GDateTime *dt);
+gboolean gsd_night_light_frac_day_is_between (gdouble value,
+ gdouble start,
+ gdouble end);
+
+G_END_DECLS
+
+#endif /* __GSD_NIGHT_LIGHT_COMMON_H */
diff --git a/plugins/color/gsd-night-light.c b/plugins/color/gsd-night-light.c
new file mode 100644
index 0000000..b11f075
--- /dev/null
+++ b/plugins/color/gsd-night-light.c
@@ -0,0 +1,807 @@
+/*
+ * Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <geoclue.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include "gnome-datetime-source.h"
+
+#include "gsd-color-state.h"
+
+#include "gsd-night-light.h"
+#include "gsd-night-light-common.h"
+
+struct _GsdNightLight {
+ GObject parent;
+ GSettings *settings;
+ gboolean forced;
+ gboolean disabled_until_tmw;
+ GDateTime *disabled_until_tmw_dt;
+ gboolean geoclue_enabled;
+ GSource *source;
+ guint validate_id;
+ GClueClient *geoclue_client;
+ GClueSimple *geoclue_simple;
+ GSettings *location_settings;
+ gdouble cached_sunrise;
+ gdouble cached_sunset;
+ gdouble cached_temperature;
+ gboolean cached_active;
+ gboolean smooth_enabled;
+ GTimer *smooth_timer;
+ guint smooth_id;
+ gdouble smooth_target_temperature;
+ GCancellable *cancellable;
+ GDateTime *datetime_override;
+};
+
+enum {
+ PROP_0,
+ PROP_ACTIVE,
+ PROP_SUNRISE,
+ PROP_SUNSET,
+ PROP_TEMPERATURE,
+ PROP_DISABLED_UNTIL_TMW,
+ PROP_FORCED,
+ PROP_LAST
+};
+
+#define GSD_NIGHT_LIGHT_SCHEDULE_TIMEOUT 5 /* seconds */
+#define GSD_NIGHT_LIGHT_POLL_TIMEOUT 60 /* seconds */
+#define GSD_NIGHT_LIGHT_POLL_SMEAR 1 /* hours */
+#define GSD_NIGHT_LIGHT_SMOOTH_SMEAR 5.f /* seconds */
+
+#define GSD_FRAC_DAY_MAX_DELTA (1.f/60.f) /* 1 minute */
+#define GSD_TEMPERATURE_MAX_DELTA (10.f) /* Kelvin */
+
+#define DESKTOP_ID "gnome-color-panel"
+
+static void poll_timeout_destroy (GsdNightLight *self);
+static void poll_timeout_create (GsdNightLight *self);
+static void night_light_recheck (GsdNightLight *self);
+
+G_DEFINE_TYPE (GsdNightLight, gsd_night_light, G_TYPE_OBJECT);
+
+static GDateTime *
+gsd_night_light_get_date_time_now (GsdNightLight *self)
+{
+ if (self->datetime_override != NULL)
+ return g_date_time_ref (self->datetime_override);
+ return g_date_time_new_now_local ();
+}
+
+void
+gsd_night_light_set_date_time_now (GsdNightLight *self, GDateTime *datetime)
+{
+ if (self->datetime_override != NULL)
+ g_date_time_unref (self->datetime_override);
+ self->datetime_override = g_date_time_ref (datetime);
+
+ night_light_recheck (self);
+}
+
+static void
+poll_smooth_destroy (GsdNightLight *self)
+{
+ if (self->smooth_id != 0) {
+ g_source_remove (self->smooth_id);
+ self->smooth_id = 0;
+ }
+ if (self->smooth_timer != NULL)
+ g_clear_pointer (&self->smooth_timer, g_timer_destroy);
+}
+
+void
+gsd_night_light_set_smooth_enabled (GsdNightLight *self,
+ gboolean smooth_enabled)
+{
+ /* ensure the timeout is stopped if called at runtime */
+ if (!smooth_enabled)
+ poll_smooth_destroy (self);
+ self->smooth_enabled = smooth_enabled;
+}
+
+static gdouble
+linear_interpolate (gdouble val1, gdouble val2, gdouble factor)
+{
+ g_return_val_if_fail (factor >= 0.f, -1.f);
+ g_return_val_if_fail (factor <= 1.f, -1.f);
+ return ((val1 - val2) * factor) + val2;
+}
+
+static gboolean
+update_cached_sunrise_sunset (GsdNightLight *self)
+{
+ gboolean ret = FALSE;
+ gdouble latitude;
+ gdouble longitude;
+ gdouble sunrise;
+ gdouble sunset;
+ g_autoptr(GVariant) tmp = NULL;
+ g_autoptr(GDateTime) dt_now = gsd_night_light_get_date_time_now (self);
+
+ /* calculate the sunrise/sunset for the location */
+ tmp = g_settings_get_value (self->settings, "night-light-last-coordinates");
+ g_variant_get (tmp, "(dd)", &latitude, &longitude);
+ if (latitude > 90.f || latitude < -90.f)
+ return FALSE;
+ if (longitude > 180.f || longitude < -180.f)
+ return FALSE;
+ if (!gsd_night_light_get_sunrise_sunset (dt_now, latitude, longitude,
+ &sunrise, &sunset)) {
+ g_warning ("failed to get sunset/sunrise for %.3f,%.3f",
+ longitude, longitude);
+ return FALSE;
+ }
+
+ /* anything changed */
+ if (ABS (self->cached_sunrise - sunrise) > GSD_FRAC_DAY_MAX_DELTA) {
+ self->cached_sunrise = sunrise;
+ g_object_notify (G_OBJECT (self), "sunrise");
+ ret = TRUE;
+ }
+ if (ABS (self->cached_sunset - sunset) > GSD_FRAC_DAY_MAX_DELTA) {
+ self->cached_sunset = sunset;
+ g_object_notify (G_OBJECT (self), "sunset");
+ ret = TRUE;
+ }
+ return ret;
+}
+
+static void
+gsd_night_light_set_temperature_internal (GsdNightLight *self, gdouble temperature, gboolean force)
+{
+ if (!force && ABS (self->cached_temperature - temperature) <= GSD_TEMPERATURE_MAX_DELTA)
+ return;
+ if (self->cached_temperature == temperature)
+ return;
+ self->cached_temperature = temperature;
+ g_object_notify (G_OBJECT (self), "temperature");
+}
+
+static gboolean
+gsd_night_light_smooth_cb (gpointer user_data)
+{
+ GsdNightLight *self = GSD_NIGHT_LIGHT (user_data);
+ gdouble tmp;
+ gdouble frac;
+
+ /* find fraction */
+ frac = g_timer_elapsed (self->smooth_timer, NULL) / GSD_NIGHT_LIGHT_SMOOTH_SMEAR;
+ if (frac >= 1.f) {
+ gsd_night_light_set_temperature_internal (self,
+ self->smooth_target_temperature,
+ TRUE);
+ self->smooth_id = 0;
+ return G_SOURCE_REMOVE;
+ }
+
+ /* set new temperature step using log curve */
+ tmp = self->smooth_target_temperature - self->cached_temperature;
+ tmp *= frac;
+ tmp += self->cached_temperature;
+ gsd_night_light_set_temperature_internal (self, tmp, FALSE);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+poll_smooth_create (GsdNightLight *self, gdouble temperature)
+{
+ g_assert (self->smooth_id == 0);
+ self->smooth_target_temperature = temperature;
+ self->smooth_timer = g_timer_new ();
+ self->smooth_id = g_timeout_add (50, gsd_night_light_smooth_cb, self);
+}
+
+static void
+gsd_night_light_set_temperature (GsdNightLight *self, gdouble temperature)
+{
+ /* immediate */
+ if (!self->smooth_enabled) {
+ gsd_night_light_set_temperature_internal (self, temperature, TRUE);
+ return;
+ }
+
+ /* Destroy any smooth transition, it will be recreated if neccessary */
+ poll_smooth_destroy (self);
+
+ /* small jump */
+ if (ABS (temperature - self->cached_temperature) < GSD_TEMPERATURE_MAX_DELTA) {
+ gsd_night_light_set_temperature_internal (self, temperature, TRUE);
+ return;
+ }
+
+ /* smooth out the transition */
+ poll_smooth_create (self, temperature);
+}
+
+static void
+gsd_night_light_set_active (GsdNightLight *self, gboolean active)
+{
+ if (self->cached_active == active)
+ return;
+ self->cached_active = active;
+
+ /* ensure set to unity temperature */
+ if (!active)
+ gsd_night_light_set_temperature (self, GSD_COLOR_TEMPERATURE_DEFAULT);
+
+ g_object_notify (G_OBJECT (self), "active");
+}
+
+static void
+night_light_recheck (GsdNightLight *self)
+{
+ gdouble frac_day;
+ gdouble schedule_from = -1.f;
+ gdouble schedule_to = -1.f;
+ gdouble smear = GSD_NIGHT_LIGHT_POLL_SMEAR; /* hours */
+ guint temperature;
+ guint temp_smeared;
+ g_autoptr(GDateTime) dt_now = gsd_night_light_get_date_time_now (self);
+
+ /* Forced mode, just set the temperature to night light.
+ * Proper rechecking will happen once forced mode is disabled again */
+ if (self->forced) {
+ temperature = g_settings_get_uint (self->settings, "night-light-temperature");
+ gsd_night_light_set_temperature (self, temperature);
+ return;
+ }
+
+ /* enabled */
+ if (!g_settings_get_boolean (self->settings, "night-light-enabled")) {
+ g_debug ("night light disabled, resetting");
+ gsd_night_light_set_active (self, FALSE);
+ return;
+ }
+
+ /* calculate the position of the sun */
+ if (g_settings_get_boolean (self->settings, "night-light-schedule-automatic")) {
+ update_cached_sunrise_sunset (self);
+ if (self->cached_sunrise > 0.f && self->cached_sunset > 0.f) {
+ schedule_to = self->cached_sunrise;
+ schedule_from = self->cached_sunset;
+ }
+ }
+
+ /* fall back to manual settings */
+ if (schedule_to <= 0.f || schedule_from <= 0.f) {
+ schedule_from = g_settings_get_double (self->settings,
+ "night-light-schedule-from");
+ schedule_to = g_settings_get_double (self->settings,
+ "night-light-schedule-to");
+ }
+
+ /* get the current hour of a day as a fraction */
+ frac_day = gsd_night_light_frac_day_from_dt (dt_now);
+ g_debug ("fractional day = %.3f, limits = %.3f->%.3f",
+ frac_day, schedule_from, schedule_to);
+
+ /* disabled until tomorrow */
+ if (self->disabled_until_tmw) {
+ GTimeSpan time_span;
+ gboolean reset = FALSE;
+
+ time_span = g_date_time_difference (dt_now, self->disabled_until_tmw_dt);
+
+ /* Reset if disabled until tomorrow is more than 24h ago. */
+ if (time_span > (GTimeSpan) 24 * 60 * 60 * 1000000) {
+ g_debug ("night light disabled until tomorrow is older than 24h, resetting disabled until tomorrow");
+ reset = TRUE;
+ } else if (time_span > 0) {
+ /* Or if a sunrise lies between the time it was disabled and now. */
+ gdouble frac_disabled;
+ frac_disabled = gsd_night_light_frac_day_from_dt (self->disabled_until_tmw_dt);
+ if (frac_disabled != frac_day &&
+ gsd_night_light_frac_day_is_between (schedule_to,
+ frac_disabled,
+ frac_day)) {
+ g_debug ("night light sun rise happened, resetting disabled until tomorrow");
+ reset = TRUE;
+ }
+ }
+
+ if (reset) {
+ self->disabled_until_tmw = FALSE;
+ g_clear_pointer(&self->disabled_until_tmw_dt, g_date_time_unref);
+ g_object_notify (G_OBJECT (self), "disabled-until-tmw");
+ }
+ }
+
+ /* lower smearing period to be smaller than the time between start/stop */
+ smear = MIN (smear,
+ MIN ( ABS (schedule_to - schedule_from),
+ 24 - ABS (schedule_to - schedule_from)));
+
+ if (!gsd_night_light_frac_day_is_between (frac_day,
+ schedule_from - smear,
+ schedule_to)) {
+ g_debug ("not time for night-light");
+ gsd_night_light_set_active (self, FALSE);
+ return;
+ }
+
+ gsd_night_light_set_active (self, TRUE);
+
+ if (self->disabled_until_tmw) {
+ g_debug ("night light still day-disabled");
+ gsd_night_light_set_temperature (self, GSD_COLOR_TEMPERATURE_DEFAULT);
+ return;
+ }
+
+ /* smear the temperature for a short duration before the set limits
+ *
+ * |----------------------| = from->to
+ * |-| = smear down
+ * |-| = smear up
+ *
+ * \ /
+ * \ /
+ * \--------------------/
+ */
+ temperature = g_settings_get_uint (self->settings, "night-light-temperature");
+ if (smear < 0.01) {
+ /* Don't try to smear for extremely short or zero periods */
+ temp_smeared = temperature;
+ } else if (gsd_night_light_frac_day_is_between (frac_day,
+ schedule_from - smear,
+ schedule_from)) {
+ gdouble factor = 1.f - ((frac_day - (schedule_from - smear)) / smear);
+ temp_smeared = linear_interpolate (GSD_COLOR_TEMPERATURE_DEFAULT,
+ temperature, factor);
+ } else if (gsd_night_light_frac_day_is_between (frac_day,
+ schedule_to - smear,
+ schedule_to)) {
+ gdouble factor = (frac_day - (schedule_to - smear)) / smear;
+ temp_smeared = linear_interpolate (GSD_COLOR_TEMPERATURE_DEFAULT,
+ temperature, factor);
+ } else {
+ temp_smeared = temperature;
+ }
+
+ g_debug ("night light mode on, using temperature of %uK (aiming for %uK)",
+ temp_smeared, temperature);
+ gsd_night_light_set_temperature (self, temp_smeared);
+}
+
+static gboolean
+night_light_recheck_schedule_cb (gpointer user_data)
+{
+ GsdNightLight *self = GSD_NIGHT_LIGHT (user_data);
+ night_light_recheck (self);
+ self->validate_id = 0;
+ return G_SOURCE_REMOVE;
+}
+
+/* called when something changed */
+static void
+night_light_recheck_schedule (GsdNightLight *self)
+{
+ if (self->validate_id != 0)
+ g_source_remove (self->validate_id);
+ self->validate_id =
+ g_timeout_add_seconds (GSD_NIGHT_LIGHT_SCHEDULE_TIMEOUT,
+ night_light_recheck_schedule_cb,
+ self);
+}
+
+/* called when the time may have changed */
+static gboolean
+night_light_recheck_cb (gpointer user_data)
+{
+ GsdNightLight *self = GSD_NIGHT_LIGHT (user_data);
+
+ /* recheck parameters, then reschedule a new timeout */
+ night_light_recheck (self);
+ poll_timeout_destroy (self);
+ poll_timeout_create (self);
+
+ /* return value ignored for a one-time watch */
+ return G_SOURCE_REMOVE;
+}
+
+static void
+poll_timeout_create (GsdNightLight *self)
+{
+ g_autoptr(GDateTime) dt_now = NULL;
+ g_autoptr(GDateTime) dt_expiry = NULL;
+
+ if (self->source != NULL)
+ return;
+
+ /* It is not a good idea to make this overridable, it just creates
+ * an infinite loop as a fixed date for testing just doesn't work. */
+ dt_now = g_date_time_new_now_local ();
+ dt_expiry = g_date_time_add_seconds (dt_now, GSD_NIGHT_LIGHT_POLL_TIMEOUT);
+ self->source = _gnome_datetime_source_new (dt_now,
+ dt_expiry,
+ TRUE);
+ g_source_set_callback (self->source,
+ night_light_recheck_cb,
+ self, NULL);
+ g_source_attach (self->source, NULL);
+}
+
+static void
+poll_timeout_destroy (GsdNightLight *self)
+{
+
+ if (self->source == NULL)
+ return;
+
+ g_source_destroy (self->source);
+ g_source_unref (self->source);
+ self->source = NULL;
+}
+
+static void
+settings_changed_cb (GSettings *settings, gchar *key, gpointer user_data)
+{
+ GsdNightLight *self = GSD_NIGHT_LIGHT (user_data);
+ g_debug ("settings changed");
+ night_light_recheck (self);
+}
+
+static void
+on_location_notify (GClueSimple *simple,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GsdNightLight *self = GSD_NIGHT_LIGHT (user_data);
+ GClueLocation *location;
+ gdouble latitude, longitude;
+
+ location = gclue_simple_get_location (simple);
+ latitude = gclue_location_get_latitude (location);
+ longitude = gclue_location_get_longitude (location);
+
+ g_settings_set_value (self->settings,
+ "night-light-last-coordinates",
+ g_variant_new ("(dd)", latitude, longitude));
+
+ g_debug ("got geoclue latitude %f, longitude %f", latitude, longitude);
+
+ /* recheck the levels if the location changed significantly */
+ if (update_cached_sunrise_sunset (self))
+ night_light_recheck_schedule (self);
+}
+
+static void
+on_geoclue_simple_ready (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsdNightLight *self = GSD_NIGHT_LIGHT (user_data);
+ GClueSimple *geoclue_simple;
+ g_autoptr(GError) error = NULL;
+
+ geoclue_simple = gclue_simple_new_finish (res, &error);
+ if (geoclue_simple == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Failed to connect to GeoClue2 service: %s", error->message);
+ return;
+ }
+
+ self->geoclue_simple = geoclue_simple;
+ self->geoclue_client = gclue_simple_get_client (self->geoclue_simple);
+ g_object_set (G_OBJECT (self->geoclue_client),
+ "time-threshold", 60*60, NULL); /* 1 hour */
+
+ g_signal_connect (self->geoclue_simple, "notify::location",
+ G_CALLBACK (on_location_notify), user_data);
+
+ on_location_notify (self->geoclue_simple, NULL, user_data);
+}
+
+static void
+start_geoclue (GsdNightLight *self)
+{
+ self->cancellable = g_cancellable_new ();
+ gclue_simple_new (DESKTOP_ID,
+ GCLUE_ACCURACY_LEVEL_CITY,
+ self->cancellable,
+ on_geoclue_simple_ready,
+ self);
+
+}
+
+static void
+stop_geoclue (GsdNightLight *self)
+{
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+
+ if (self->geoclue_client != NULL) {
+ gclue_client_call_stop (self->geoclue_client, NULL, NULL, NULL);
+ self->geoclue_client = NULL;
+ }
+ g_clear_object (&self->geoclue_simple);
+}
+
+static void
+check_location_settings (GsdNightLight *self)
+{
+ if (g_settings_get_boolean (self->location_settings, "enabled") && self->geoclue_enabled)
+ start_geoclue (self);
+ else
+ stop_geoclue (self);
+}
+
+void
+gsd_night_light_set_disabled_until_tmw (GsdNightLight *self, gboolean value)
+{
+ g_autoptr(GDateTime) dt = gsd_night_light_get_date_time_now (self);
+
+ if (self->disabled_until_tmw == value)
+ return;
+
+ self->disabled_until_tmw = value;
+ g_clear_pointer (&self->disabled_until_tmw_dt, g_date_time_unref);
+ if (self->disabled_until_tmw)
+ self->disabled_until_tmw_dt = g_steal_pointer (&dt);
+ night_light_recheck (self);
+ g_object_notify (G_OBJECT (self), "disabled-until-tmw");
+}
+
+gboolean
+gsd_night_light_get_disabled_until_tmw (GsdNightLight *self)
+{
+ return self->disabled_until_tmw;
+}
+
+void
+gsd_night_light_set_forced (GsdNightLight *self, gboolean value)
+{
+ if (self->forced == value)
+ return;
+
+ self->forced = value;
+ g_object_notify (G_OBJECT (self), "forced");
+
+ /* A simple recheck might not reset the temperature if
+ * night light is currently disabled. */
+ if (!self->forced && !self->cached_active)
+ gsd_night_light_set_temperature (self, GSD_COLOR_TEMPERATURE_DEFAULT);
+
+ night_light_recheck (self);
+}
+
+gboolean
+gsd_night_light_get_forced (GsdNightLight *self)
+{
+ return self->forced;
+}
+
+gboolean
+gsd_night_light_get_active (GsdNightLight *self)
+{
+ return self->cached_active;
+}
+
+gdouble
+gsd_night_light_get_sunrise (GsdNightLight *self)
+{
+ return self->cached_sunrise;
+}
+
+gdouble
+gsd_night_light_get_sunset (GsdNightLight *self)
+{
+ return self->cached_sunset;
+}
+
+gdouble
+gsd_night_light_get_temperature (GsdNightLight *self)
+{
+ return self->cached_temperature;
+}
+
+void
+gsd_night_light_set_geoclue_enabled (GsdNightLight *self, gboolean enabled)
+{
+ self->geoclue_enabled = enabled;
+}
+
+gboolean
+gsd_night_light_start (GsdNightLight *self, GError **error)
+{
+ night_light_recheck (self);
+ poll_timeout_create (self);
+
+ /* care about changes */
+ g_signal_connect (self->settings, "changed",
+ G_CALLBACK (settings_changed_cb), self);
+
+ g_signal_connect_swapped (self->location_settings, "changed::enabled",
+ G_CALLBACK (check_location_settings), self);
+ check_location_settings (self);
+
+ return TRUE;
+}
+
+static void
+gsd_night_light_finalize (GObject *object)
+{
+ GsdNightLight *self = GSD_NIGHT_LIGHT (object);
+
+ stop_geoclue (self);
+
+ poll_timeout_destroy (self);
+ poll_smooth_destroy (self);
+
+ g_clear_object (&self->settings);
+ g_clear_pointer (&self->datetime_override, g_date_time_unref);
+ g_clear_pointer (&self->disabled_until_tmw_dt, g_date_time_unref);
+
+ if (self->validate_id > 0) {
+ g_source_remove (self->validate_id);
+ self->validate_id = 0;
+ }
+
+ g_clear_object (&self->location_settings);
+ G_OBJECT_CLASS (gsd_night_light_parent_class)->finalize (object);
+}
+
+static void
+gsd_night_light_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsdNightLight *self = GSD_NIGHT_LIGHT (object);
+
+ switch (prop_id) {
+ case PROP_SUNRISE:
+ self->cached_sunrise = g_value_get_double (value);
+ break;
+ case PROP_SUNSET:
+ self->cached_sunset = g_value_get_double (value);
+ break;
+ case PROP_TEMPERATURE:
+ self->cached_temperature = g_value_get_double (value);
+ break;
+ case PROP_DISABLED_UNTIL_TMW:
+ gsd_night_light_set_disabled_until_tmw (self, g_value_get_boolean (value));
+ break;
+ case PROP_FORCED:
+ gsd_night_light_set_forced (self, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gsd_night_light_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsdNightLight *self = GSD_NIGHT_LIGHT (object);
+
+ switch (prop_id) {
+ case PROP_ACTIVE:
+ g_value_set_boolean (value, self->cached_active);
+ break;
+ case PROP_SUNRISE:
+ g_value_set_double (value, self->cached_sunrise);
+ break;
+ case PROP_SUNSET:
+ g_value_set_double (value, self->cached_sunset);
+ break;
+ case PROP_TEMPERATURE:
+ g_value_set_double (value, self->cached_temperature);
+ break;
+ case PROP_DISABLED_UNTIL_TMW:
+ g_value_set_boolean (value, gsd_night_light_get_disabled_until_tmw (self));
+ break;
+ case PROP_FORCED:
+ g_value_set_boolean (value, gsd_night_light_get_forced (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gsd_night_light_class_init (GsdNightLightClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = gsd_night_light_finalize;
+
+ object_class->set_property = gsd_night_light_set_property;
+ object_class->get_property = gsd_night_light_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_ACTIVE,
+ g_param_spec_boolean ("active",
+ "Active",
+ "If night light functionality is active right now",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_SUNRISE,
+ g_param_spec_double ("sunrise",
+ "Sunrise",
+ "Sunrise in fractional hours",
+ 0,
+ 24.f,
+ 12,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SUNSET,
+ g_param_spec_double ("sunset",
+ "Sunset",
+ "Sunset in fractional hours",
+ 0,
+ 24.f,
+ 12,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_TEMPERATURE,
+ g_param_spec_double ("temperature",
+ "Temperature",
+ "Temperature in Kelvin",
+ GSD_COLOR_TEMPERATURE_MIN,
+ GSD_COLOR_TEMPERATURE_MAX,
+ GSD_COLOR_TEMPERATURE_DEFAULT,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_DISABLED_UNTIL_TMW,
+ g_param_spec_boolean ("disabled-until-tmw",
+ "Disabled until tomorrow",
+ "If the night light is disabled until the next day",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_FORCED,
+ g_param_spec_boolean ("forced",
+ "Forced",
+ "Whether night light should be forced on, useful for previewing",
+ FALSE,
+ G_PARAM_READWRITE));
+
+}
+
+static void
+gsd_night_light_init (GsdNightLight *self)
+{
+ self->geoclue_enabled = TRUE;
+ self->smooth_enabled = TRUE;
+ self->cached_sunrise = -1.f;
+ self->cached_sunset = -1.f;
+ self->cached_temperature = GSD_COLOR_TEMPERATURE_DEFAULT;
+ self->settings = g_settings_new ("org.gnome.settings-daemon.plugins.color");
+ self->location_settings = g_settings_new ("org.gnome.system.location");
+}
+
+GsdNightLight *
+gsd_night_light_new (void)
+{
+ return g_object_new (GSD_TYPE_NIGHT_LIGHT, NULL);
+}
diff --git a/plugins/color/gsd-night-light.h b/plugins/color/gsd-night-light.h
new file mode 100644
index 0000000..8803c33
--- /dev/null
+++ b/plugins/color/gsd-night-light.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GSD_NIGHT_LIGHT_H__
+#define __GSD_NIGHT_LIGHT_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_NIGHT_LIGHT (gsd_night_light_get_type ())
+G_DECLARE_FINAL_TYPE (GsdNightLight, gsd_night_light, GSD, NIGHT_LIGHT, GObject)
+
+GsdNightLight *gsd_night_light_new (void);
+gboolean gsd_night_light_start (GsdNightLight *self,
+ GError **error);
+
+gboolean gsd_night_light_get_active (GsdNightLight *self);
+gdouble gsd_night_light_get_sunrise (GsdNightLight *self);
+gdouble gsd_night_light_get_sunset (GsdNightLight *self);
+gdouble gsd_night_light_get_temperature (GsdNightLight *self);
+
+gboolean gsd_night_light_get_disabled_until_tmw (GsdNightLight *self);
+void gsd_night_light_set_disabled_until_tmw (GsdNightLight *self,
+ gboolean value);
+
+gboolean gsd_night_light_get_forced (GsdNightLight *self);
+void gsd_night_light_set_forced (GsdNightLight *self,
+ gboolean value);
+
+/* only for the self test program */
+void gsd_night_light_set_geoclue_enabled (GsdNightLight *self,
+ gboolean enabled);
+void gsd_night_light_set_date_time_now (GsdNightLight *self,
+ GDateTime *datetime);
+void gsd_night_light_set_smooth_enabled (GsdNightLight *self,
+ gboolean smooth_enabled);
+
+G_END_DECLS
+
+#endif
diff --git a/plugins/color/main.c b/plugins/color/main.c
new file mode 100644
index 0000000..5dda3e7
--- /dev/null
+++ b/plugins/color/main.c
@@ -0,0 +1,7 @@
+#define NEW gsd_color_manager_new
+#define START gsd_color_manager_start
+#define STOP gsd_color_manager_stop
+#define MANAGER GsdColorManager
+#include "gsd-color-manager.h"
+
+#include "daemon-skeleton-gtk.h"
diff --git a/plugins/color/meson.build b/plugins/color/meson.build
new file mode 100644
index 0000000..b454bbb
--- /dev/null
+++ b/plugins/color/meson.build
@@ -0,0 +1,50 @@
+sources = files(
+ 'gnome-datetime-source.c',
+ 'gsd-color-calibrate.c',
+ 'gsd-color-manager.c',
+ 'gsd-color-state.c',
+ 'gsd-night-light.c',
+ 'gsd-night-light-common.c',
+ 'main.c'
+)
+
+deps = plugins_deps + [
+ colord_dep,
+ libcanberra_gtk_dep,
+ libgeoclue_dep,
+ libnotify_dep,
+ m_dep,
+]
+
+cflags += ['-DBINDIR="@0@"'.format(gsd_bindir)]
+
+executable(
+ 'gsd-' + plugin_name,
+ sources,
+ include_directories: [top_inc, common_inc],
+ dependencies: deps,
+ c_args: cflags,
+ install: true,
+ install_rpath: gsd_pkglibdir,
+ install_dir: gsd_libexecdir
+)
+
+sources = files(
+ 'gcm-self-test.c',
+ 'gnome-datetime-source.c',
+ 'gsd-night-light.c',
+ 'gsd-night-light-common.c'
+)
+
+test_unit = 'gcm-self-test'
+
+exe = executable(
+ test_unit,
+ sources,
+ include_directories: top_inc,
+ dependencies: deps,
+ c_args: '-DTESTDATADIR="@0@"'.format(join_paths(meson.current_source_dir(), 'test-data'))
+)
+
+envs = ['GSETTINGS_SCHEMA_DIR=@0@'.format(join_paths(meson.build_root(), 'data'))]
+test(test_unit, exe, env: envs)
diff --git a/plugins/color/test-data/LG-L225W-External.bin b/plugins/color/test-data/LG-L225W-External.bin
new file mode 100644
index 0000000..f08310a
--- /dev/null
+++ b/plugins/color/test-data/LG-L225W-External.bin
Binary files differ
diff --git a/plugins/color/test-data/Lenovo-T61-Internal.bin b/plugins/color/test-data/Lenovo-T61-Internal.bin
new file mode 100644
index 0000000..45aec9d
--- /dev/null
+++ b/plugins/color/test-data/Lenovo-T61-Internal.bin
Binary files differ