[gnome-settings-daemon] color: Add natural light functionality



commit 749c9cf675906483ebd29beb5a4ac379a7613d3f
Author: Richard Hughes <richard hughsie com>
Date:   Tue Jan 31 16:54:09 2017 +0000

    color: Add natural light functionality
    
    https://bugzilla.gnome.org/show_bug.cgi?id=778039

 configure.ac                                       |    1 +
 ...settings-daemon.plugins.color.gschema.xml.in.in |   30 +
 plugins/color/Makefile.am                          |    6 +
 plugins/color/gcm-self-test.c                      |  114 ++++
 plugins/color/gsd-color-manager.c                  |  128 ++++-
 plugins/color/gsd-natural-light.c                  |  577 ++++++++++++++++++++
 plugins/color/gsd-natural-light.h                  |   50 ++
 7 files changed, 899 insertions(+), 7 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 6e0d0dc..b42b644 100644
--- a/configure.ac
+++ b/configure.ac
@@ -161,6 +161,7 @@ PKG_CHECK_MODULES(COLOR,
                  colord >= 1.0.2
                  gnome-desktop-3.0 >= $GNOME_DESKTOP_REQUIRED_VERSION
                  libcanberra-gtk3
+                 libgeoclue-2.0 >= $GEOCLUE_REQUIRED_VERSION
                  lcms2 >= $LCMS_REQUIRED_VERSION
                  libnotify)
 
diff --git a/data/org.gnome.settings-daemon.plugins.color.gschema.xml.in.in 
b/data/org.gnome.settings-daemon.plugins.color.gschema.xml.in.in
index 3df07c7..7fb36a0 100644
--- a/data/org.gnome.settings-daemon.plugins.color.gschema.xml.in.in
+++ b/data/org.gnome.settings-daemon.plugins.color.gschema.xml.in.in
@@ -10,5 +10,35 @@
       <_summary>The duration a printer profile is valid</_summary>
       <_description>This is the number of days after which the printer color profile is considered 
invalid.</_description>
     </key>
+    <key name="natural-light-enabled" type="b">
+      <default>false</default>
+      <_summary>If the natural night mode is enabled</_summary>
+      <_description>Natural light mode changes the color temperature of your display when the sun has gone 
down or at present times.</_description>
+    </key>
+    <key name="natural-light-temperature" type="u">
+      <default>4000</default>
+      <_summary>Temperature of the display when enabled</_summary>
+      <_description>This temperature in Kelvin is used to modify the screen tones when natural light mode is 
enabled. Higher values are bluer, lower redder.</_description>
+    </key>
+    <key name="natural-light-schedule-automatic" type="b">
+      <default>true</default>
+      <_summary>Use the sunrise and sunset</_summary>
+      <_description>Calculate the sunrise and sunset times automatically, from the current 
location.</_description>
+    </key>
+    <key name="natural-light-schedule-from" type="d">
+      <default>16.00</default>
+      <_summary>The start time</_summary>
+      <_description>When “natural-light-schedule-automatic” is disabled, use this start time in hours from 
midnight.</_description>
+    </key>
+    <key name="natural-light-schedule-to" type="d">
+      <default>8.00</default>
+      <_summary>The end time</_summary>
+      <_description>When “natural-light-schedule-automatic” is disabled, use this end time in hours from 
midnight.</_description>
+    </key>
+    <key name="natural-light-last-coordinates" type="(dd)">
+      <default>(181,181)</default>
+      <_summary>The last detected position</_summary>
+      <_description>When location services are available this represents the last detected location. The 
default value is an invalid value to ensure it is always updated at startup.</_description>
+    </key>
   </schema>
 </schemalist>
diff --git a/plugins/color/Makefile.am b/plugins/color/Makefile.am
index a8e9567..042b245 100644
--- a/plugins/color/Makefile.am
+++ b/plugins/color/Makefile.am
@@ -15,8 +15,12 @@ gcm_self_test_CFLAGS =                       \
 gcm_self_test_SOURCES =                        \
        gcm-edid.c                      \
        gcm-edid.h                      \
+       gsd-natural-light.c             \
+       gsd-natural-light.h             \
        gsd-natural-light-common.c      \
        gsd-natural-light-common.h      \
+       gnome-datetime-source.c         \
+       gnome-datetime-source.h         \
        gcm-self-test.c
 
 gcm_self_test_LDADD =                  \
@@ -41,6 +45,8 @@ gsd_color_SOURCES =                   \
        gsd-color-profiles.h            \
        gsd-color-state.c               \
        gsd-color-state.h               \
+       gsd-natural-light.c             \
+       gsd-natural-light.h             \
        gsd-natural-light-common.c      \
        gsd-natural-light-common.h      \
        $(NULL)
diff --git a/plugins/color/gcm-self-test.c b/plugins/color/gcm-self-test.c
index a5163a7..efb2101 100644
--- a/plugins/color/gcm-self-test.c
+++ b/plugins/color/gcm-self-test.c
@@ -26,9 +26,120 @@
 #include <gtk/gtk.h>
 
 #include "gcm-edid.h"
+#include "gsd-color-state.h"
+#include "gsd-natural-light.h"
 #include "gsd-natural-light-common.h"
 
 static void
+on_notify (GsdNaturalLight *nlight,
+           GParamSpec      *pspec,
+           gpointer         user_data)
+{
+        guint *cnt = (guint *) user_data;
+        (*cnt)++;
+}
+
+static void
+gcm_test_natural_light (void)
+{
+        gboolean ret;
+        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(GsdNaturalLight) nlight = NULL;
+        g_autoptr(GSettings) settings = NULL;
+
+        nlight = gsd_natural_light_new ();
+        g_assert (GSD_IS_NATURAL_LIGHT (nlight));
+        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_natural_light_set_date_time_now (nlight, datetime_override);
+
+        /* do not start geoclue */
+        gsd_natural_light_set_geoclue_enabled (nlight, FALSE);
+
+        /* switch off */
+        settings = g_settings_new ("org.gnome.settings-daemon.plugins.color");
+        g_settings_set_boolean (settings, "natural-light-enabled", FALSE);
+
+        /* check default values */
+        g_assert_cmpint ((gint) gsd_natural_light_get_sunrise (nlight), ==, -1);
+        g_assert_cmpint ((gint) gsd_natural_light_get_sunset (nlight), ==, -1);
+        g_assert_cmpint (gsd_natural_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT);
+        g_assert (!gsd_natural_light_get_disabled_until_tmw (nlight));
+
+        /* start module, disabled */
+        ret = gsd_natural_light_start (nlight, &error);
+        g_assert_no_error (error);
+        g_assert (ret);
+        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, "natural-light-last-coordinates",
+                              g_variant_new ("(dd)", 51.5, -0.1278));
+        g_settings_set_boolean (settings, "natural-light-schedule-automatic", TRUE);
+        g_settings_set_boolean (settings, "natural-light-enabled", TRUE);
+        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_natural_light_get_sunrise (nlight), ==, 7);
+        g_assert_cmpint ((gint) gsd_natural_light_get_sunset (nlight), ==, 17);
+        g_assert_cmpint (gsd_natural_light_get_temperature (nlight), ==, 4000);
+        g_assert (!gsd_natural_light_get_disabled_until_tmw (nlight));
+
+        /* disable for one day */
+        gsd_natural_light_set_disabled_until_tmw (nlight, TRUE);
+        gsd_natural_light_set_disabled_until_tmw (nlight, TRUE);
+        g_assert_cmpint (gsd_natural_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT);
+        g_assert (gsd_natural_light_get_disabled_until_tmw (nlight));
+        g_assert_cmpint (temperature_cnt, ==, 2);
+        g_assert_cmpint (disabled_until_tmw_cnt, ==, 1);
+
+        /* change our mind */
+        gsd_natural_light_set_disabled_until_tmw (nlight, FALSE);
+        g_assert_cmpint (gsd_natural_light_get_temperature (nlight), ==, 4000);
+        g_assert (!gsd_natural_light_get_disabled_until_tmw (nlight));
+        g_assert_cmpint (temperature_cnt, ==, 3);
+        g_assert_cmpint (disabled_until_tmw_cnt, ==, 2);
+
+        /* enabled manual mode (night shift) */
+        g_settings_set_double (settings, "natural-light-schedule-from", 4.0);
+        g_settings_set_double (settings, "natural-light-schedule-to", 16.f);
+        g_settings_set_boolean (settings, "natural-light-schedule-automatic", FALSE);
+        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_cmpint ((gint) gsd_natural_light_get_sunrise (nlight), ==, 7);
+        g_assert_cmpint ((gint) gsd_natural_light_get_sunset (nlight), ==, 17);
+        g_assert_cmpint (gsd_natural_light_get_temperature (nlight), ==, GSD_COLOR_TEMPERATURE_DEFAULT);
+        g_assert (!gsd_natural_light_get_disabled_until_tmw (nlight));
+
+        /* finally disable, with no changes */
+        g_settings_set_boolean (settings, "natural-light-enabled", FALSE);
+        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);
+}
+
+static void
 gcm_test_edid_func (void)
 {
         GcmEdid *edid;
@@ -131,12 +242,15 @@ gcm_test_frac_day (void)
 int
 main (int argc, char **argv)
 {
+        g_setenv ("GSETTINGS_BACKEND", "memory", TRUE);
+
         gtk_init (&argc, &argv);
         g_test_init (&argc, &argv, NULL);
 
         g_test_add_func ("/color/edid", gcm_test_edid_func);
         g_test_add_func ("/color/sunset-sunrise", gcm_test_sunset_sunrise);
         g_test_add_func ("/color/fractional-day", gcm_test_frac_day);
+        g_test_add_func ("/color/natural-light", gcm_test_natural_light);
 
         return g_test_run ();
 }
diff --git a/plugins/color/gsd-color-manager.c b/plugins/color/gsd-color-manager.c
index 6a1c745..0834af5 100644
--- a/plugins/color/gsd-color-manager.c
+++ b/plugins/color/gsd-color-manager.c
@@ -29,6 +29,7 @@
 #include "gsd-color-manager.h"
 #include "gsd-color-profiles.h"
 #include "gsd-color-state.h"
+#include "gsd-natural-light.h"
 
 #define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
 #define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
@@ -42,6 +43,9 @@ static const gchar introspection_xml[] =
 "<node>"
 "  <interface name='org.gnome.SettingsDaemon.Color'>"
 "    <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>";
 
@@ -58,6 +62,7 @@ struct GsdColorManagerPrivate
         GsdColorCalibrate *calibrate;
         GsdColorProfiles  *profiles;
         GsdColorState     *state;
+        GsdNaturalLight   *nlight;
 };
 
 enum {
@@ -123,6 +128,86 @@ gsd_color_manager_class_init (GsdColorManagerClass *klass)
 }
 
 static void
+emit_property_changed (GsdColorManager *manager,
+                       const gchar *property_name,
+                       GVariant *property_value)
+{
+        GsdColorManagerPrivate *priv = manager->priv;
+        GVariantBuilder builder;
+        GVariantBuilder invalidated_builder;
+
+        /* not yet connected */
+        if (priv->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 (priv->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_sunset_notify (GsdNaturalLight *nlight,
+                  GParamSpec      *pspec,
+                  gpointer         user_data)
+{
+        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+        GsdColorManagerPrivate *priv = manager->priv;
+        emit_property_changed (manager, "Sunset",
+                               g_variant_new_double (gsd_natural_light_get_sunset (priv->nlight)));
+}
+
+static void
+on_sunrise_notify (GsdNaturalLight *nlight,
+                   GParamSpec      *pspec,
+                   gpointer         user_data)
+{
+        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+        GsdColorManagerPrivate *priv = manager->priv;
+        emit_property_changed (manager, "Sunrise",
+                               g_variant_new_double (gsd_natural_light_get_sunrise (priv->nlight)));
+}
+
+static void
+on_disabled_until_tmw_notify (GsdNaturalLight *nlight,
+                              GParamSpec      *pspec,
+                              gpointer         user_data)
+{
+        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+        GsdColorManagerPrivate *priv = manager->priv;
+        emit_property_changed (manager, "DisabledUntilTomorrow",
+                               g_variant_new_boolean (gsd_natural_light_get_disabled_until_tmw 
(priv->nlight)));
+}
+
+static void
+on_tempertature_notify (GsdNaturalLight *nlight,
+                        GParamSpec      *pspec,
+                        gpointer         user_data)
+{
+        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+        GsdColorManagerPrivate *priv = manager->priv;
+        gdouble temperature = gsd_natural_light_get_temperature (priv->nlight);
+        gsd_color_state_set_temperature (priv->state, temperature);
+        emit_property_changed (manager, "Temperature",
+                               g_variant_new_double (temperature));
+}
+
+static void
 gsd_color_manager_init (GsdColorManager *manager)
 {
         GsdColorManagerPrivate *priv;
@@ -132,6 +217,17 @@ gsd_color_manager_init (GsdColorManager *manager)
         priv->calibrate = gsd_color_calibrate_new ();
         priv->profiles = gsd_color_profiles_new ();
         priv->state = gsd_color_state_new ();
+
+        /* natural light features */
+        priv->nlight = gsd_natural_light_new ();
+        g_signal_connect (priv->nlight, "notify::sunset",
+                          G_CALLBACK (on_sunset_notify), manager);
+        g_signal_connect (priv->nlight, "notify::sunrise",
+                          G_CALLBACK (on_sunrise_notify), manager);
+        g_signal_connect (priv->nlight, "notify::temperature",
+                          G_CALLBACK (on_tempertature_notify), manager);
+        g_signal_connect (priv->nlight, "notify::disabled-until-tmw",
+                          G_CALLBACK (on_disabled_until_tmw_notify), manager);
 }
 
 static void
@@ -162,6 +258,7 @@ gsd_color_manager_finalize (GObject *object)
         g_clear_object (&manager->priv->calibrate);
         g_clear_object (&manager->priv->profiles);
         g_clear_object (&manager->priv->state);
+        g_clear_object (&manager->priv->nlight);
 
         G_OBJECT_CLASS (gsd_color_manager_parent_class)->finalize (object);
 }
@@ -174,7 +271,6 @@ handle_get_property (GDBusConnection *connection,
                      const gchar *property_name,
                      GError **error, gpointer user_data)
 {
-        GVariant *retval = NULL;
         GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
         GsdColorManagerPrivate *priv = manager->priv;
 
@@ -187,15 +283,21 @@ handle_get_property (GDBusConnection *connection,
         if (g_strcmp0 (property_name, "Temperature") == 0) {
                 guint temperature;
                 temperature = gsd_color_state_get_temperature (priv->state);
-                retval = g_variant_new_uint32 (temperature);
+                return g_variant_new_uint32 (temperature);
         }
 
-        if (retval == NULL) {
-                g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
-                             "Failed to get property: %s", property_name);
-        }
+        if (g_strcmp0 (property_name, "DisabledUntilTomorrow") == 0)
+                return g_variant_new_boolean (gsd_natural_light_get_disabled_until_tmw (priv->nlight));
+
+        if (g_strcmp0 (property_name, "Sunrise") == 0)
+                return g_variant_new_double (gsd_natural_light_get_sunrise (priv->nlight));
 
-        return retval;
+        if (g_strcmp0 (property_name, "Sunset") == 0)
+                return g_variant_new_double (gsd_natural_light_get_sunset (priv->nlight));
+
+        g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+                     "Failed to get property: %s", property_name);
+        return NULL;
 }
 
 static gboolean
@@ -239,6 +341,12 @@ handle_set_property (GDBusConnection *connection,
                 return TRUE;
         }
 
+        if (g_strcmp0 (property_name, "DisabledUntilTomorrow") == 0) {
+                gsd_natural_light_set_disabled_until_tmw (priv->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;
@@ -292,6 +400,12 @@ on_bus_gotten (GObject             *source_object,
                                                       name_lost_handler_cb,
                                                       manager,
                                                       NULL);
+
+        /* setup natural light module */
+        if (!gsd_natural_light_start (priv->nlight, &error)) {
+                g_warning ("Could not start natural light module: %s", error->message);
+                g_error_free (error);
+        }
 }
 
 static void
diff --git a/plugins/color/gsd-natural-light.c b/plugins/color/gsd-natural-light.c
new file mode 100644
index 0000000..dedbd74
--- /dev/null
+++ b/plugins/color/gsd-natural-light.c
@@ -0,0 +1,577 @@
+/*
+ * 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-natural-light.h"
+#include "gsd-natural-light-common.h"
+
+struct _GsdNaturalLight {
+        GObject            parent;
+        GSettings         *settings;
+        gboolean           disabled_until_tmw;
+        gboolean           geoclue_enabled;
+        GSource           *natural_light_source;
+        guint              natural_light_validate_id;
+        gint               disabled_day_of_month;
+        GClueClient       *geoclue_client;
+        GClueSimple       *geoclue_simple;
+        gdouble            cached_sunrise;
+        gdouble            cached_sunset;
+        gdouble            cached_temperature;
+        GCancellable      *cancellable;
+        GDateTime         *datetime_override;
+};
+
+enum {
+        PROP_0,
+        PROP_SUNRISE,
+        PROP_SUNSET,
+        PROP_TEMPERATURE,
+        PROP_DISABLED_UNTIL_TMW,
+        PROP_LAST
+};
+
+#define GSD_NATURAL_LIGHT_SCHEDULE_TIMEOUT      5       /* seconds */
+#define GSD_NATURAL_LIGHT_POLL_TIMEOUT          60      /* seconds */
+#define GSD_NATURAL_LIGHT_POLL_SMEAR            1       /* hours */
+
+#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 (GsdNaturalLight *self);
+static void poll_timeout_create (GsdNaturalLight *self);
+
+G_DEFINE_TYPE (GsdNaturalLight, gsd_natural_light, G_TYPE_OBJECT);
+
+static GDateTime *
+gsd_natural_light_get_date_time_now (GsdNaturalLight *self)
+{
+        if (self->datetime_override != NULL)
+                return g_date_time_ref (self->datetime_override);
+        return g_date_time_new_now_local ();
+}
+
+void
+gsd_natural_light_set_date_time_now (GsdNaturalLight *self, GDateTime *datetime)
+{
+        if (self->datetime_override != NULL)
+                g_date_time_unref (self->datetime_override);
+        self->datetime_override = g_date_time_ref (datetime);
+}
+
+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 (GsdNaturalLight *self)
+{
+        gboolean ret = FALSE;
+        gdouble latitude;
+        gdouble longitude;
+        gdouble sunrise;
+        gdouble sunset;
+        g_autoptr(GVariant) tmp = NULL;
+        g_autoptr(GDateTime) dt_now = gsd_natural_light_get_date_time_now (self);
+
+        /* calculate the sunrise/sunset for the location */
+        tmp = g_settings_get_value (self->settings, "natural-light-last-coordinates");
+        g_variant_get (tmp, "(dd)", &latitude, &longitude);
+        if (latitude > 180.f || latitude < -180.f)
+                return FALSE;
+        if (longitude > 180.f || longitude < -180.f)
+                return FALSE;
+        if (!gsd_natural_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_natural_light_set_temperature (GsdNaturalLight *self, gdouble temperature)
+{
+        if (ABS (self->cached_temperature - temperature) > GSD_TEMPERATURE_MAX_DELTA) {
+                self->cached_temperature = temperature;
+                g_object_notify (G_OBJECT (self), "temperature");
+        }
+}
+
+static void
+natural_light_recheck (GsdNaturalLight *self)
+{
+        gdouble frac_day;
+        gdouble schedule_from = -1.f;
+        gdouble schedule_to = -1.f;
+        gdouble smear = GSD_NATURAL_LIGHT_POLL_SMEAR; /* hours */
+        guint temperature;
+        guint temp_smeared;
+        g_autoptr(GDateTime) dt_now = gsd_natural_light_get_date_time_now (self);
+
+        /* enabled */
+        if (!g_settings_get_boolean (self->settings, "natural-light-enabled")) {
+                g_debug ("natural light disabled, resetting");
+                gsd_natural_light_set_temperature (self,
+                                                   GSD_COLOR_TEMPERATURE_DEFAULT);
+                return;
+        }
+
+        /* disabled until tomorrow */
+        if (self->disabled_until_tmw) {
+                gint tmp_day_of_month = g_date_time_get_day_of_month (dt_now);
+                if (tmp_day_of_month == self->disabled_day_of_month) {
+                        g_debug ("natural light still day-disabled, resetting");
+                        gsd_natural_light_set_temperature (self,
+                                                         GSD_COLOR_TEMPERATURE_DEFAULT);
+                        return;
+                }
+
+                /* no longer valid */
+                self->disabled_day_of_month = 0;
+                self->disabled_until_tmw = FALSE;
+                g_object_notify (G_OBJECT (self), "disabled-until-tmw");
+        }
+
+        /* calculate the position of the sun */
+        if (g_settings_get_boolean (self->settings, "natural-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,
+                                                       "natural-light-schedule-from");
+                schedule_to = g_settings_get_double (self->settings,
+                                                     "natural-light-schedule-to");
+        }
+
+        /* get the current hour of a day as a fraction */
+        frac_day = gsd_natural_light_frac_day_from_dt (dt_now);
+        g_debug ("fractional day = %.3f, limits = %.3f->%.3f",
+                 frac_day, schedule_from, schedule_to);
+        if (!gsd_natural_light_frac_day_is_between (frac_day,
+                                                    schedule_from - smear,
+                                                    schedule_to)) {
+                g_debug ("not time for natural-light");
+                gsd_natural_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, "natural-light-temperature");
+        if (gsd_natural_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_natural_light_frac_day_is_between (frac_day,
+                                                          schedule_to - smear,
+                                                          schedule_to)) {
+                gdouble factor = ((schedule_to - smear) - frac_day) / smear;
+                temp_smeared = linear_interpolate (GSD_COLOR_TEMPERATURE_DEFAULT,
+                                                   temperature, factor);
+        } else {
+                temp_smeared = temperature;
+        }
+        g_debug ("natural light mode on, using temperature of %uK (aiming for %uK)",
+                 temp_smeared, temperature);
+        gsd_natural_light_set_temperature (self, temp_smeared);
+}
+
+static gboolean
+natural_light_recheck_schedule_cb (gpointer user_data)
+{
+        GsdNaturalLight *self = GSD_NATURAL_LIGHT (user_data);
+        natural_light_recheck (self);
+        self->natural_light_validate_id = 0;
+        return G_SOURCE_REMOVE;
+}
+
+/* called when something changed */
+static void
+natural_light_recheck_schedule (GsdNaturalLight *self)
+{
+        if (self->natural_light_validate_id != 0)
+                g_source_remove (self->natural_light_validate_id);
+        self->natural_light_validate_id =
+                g_timeout_add_seconds (GSD_NATURAL_LIGHT_SCHEDULE_TIMEOUT,
+                                       natural_light_recheck_schedule_cb,
+                                       self);
+}
+
+/* called when the time may have changed */
+static gboolean
+natural_light_recheck_cb (gpointer user_data)
+{
+        GsdNaturalLight *self = GSD_NATURAL_LIGHT (user_data);
+
+        /* recheck parameters, then reschedule a new timeout */
+        natural_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 (GsdNaturalLight *self)
+{
+        g_autoptr(GDateTime) dt_now = NULL;
+        g_autoptr(GDateTime) dt_expiry = NULL;
+
+        if (self->natural_light_source != NULL)
+                return;
+
+        dt_now = gsd_natural_light_get_date_time_now (self);
+        dt_expiry = g_date_time_add_seconds (dt_now, GSD_NATURAL_LIGHT_POLL_TIMEOUT);
+        self->natural_light_source = _gnome_datetime_source_new (dt_now,
+                                                                 dt_expiry,
+                                                                 FALSE);
+        g_source_set_callback (self->natural_light_source,
+                               natural_light_recheck_cb,
+                               self, NULL);
+        g_source_attach (self->natural_light_source, NULL);
+}
+
+static void
+poll_timeout_destroy (GsdNaturalLight *self)
+{
+
+        if (self->natural_light_source == NULL)
+                return;
+
+        g_source_destroy (self->natural_light_source);
+        self->natural_light_source = NULL;
+}
+
+static void
+settings_changed_cb (GSettings *settings, gchar *key, gpointer user_data)
+{
+        GsdNaturalLight *self = GSD_NATURAL_LIGHT (user_data);
+        g_debug ("settings changed");
+        natural_light_recheck (self);
+}
+
+static void
+on_location_notify (GClueSimple *simple,
+                    GParamSpec  *pspec,
+                    gpointer     user_data)
+{
+        GsdNaturalLight *self = GSD_NATURAL_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,
+                              "natural-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))
+                natural_light_recheck_schedule (self);
+}
+
+static void
+on_geoclue_simple_ready (GObject      *source_object,
+                         GAsyncResult *res,
+                         gpointer      user_data)
+{
+        GsdNaturalLight *self = GSD_NATURAL_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_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
+register_geoclue (GsdNaturalLight *self)
+{
+        gclue_simple_new (DESKTOP_ID,
+                          GCLUE_ACCURACY_LEVEL_CITY,
+                          self->cancellable,
+                          on_geoclue_simple_ready,
+                          self);
+
+}
+
+void
+gsd_natural_light_set_disabled_until_tmw (GsdNaturalLight *self, gboolean value)
+{
+        g_autoptr(GDateTime) dt = gsd_natural_light_get_date_time_now (self);
+
+        if (self->disabled_until_tmw == value)
+                return;
+
+        self->disabled_until_tmw = value;
+        self->disabled_day_of_month = g_date_time_get_day_of_month (dt);
+        natural_light_recheck (self);
+        g_object_notify (G_OBJECT (self), "disabled-until-tmw");
+}
+
+gboolean
+gsd_natural_light_get_disabled_until_tmw (GsdNaturalLight *self)
+{
+        return self->disabled_until_tmw;
+}
+
+gdouble
+gsd_natural_light_get_sunrise (GsdNaturalLight *self)
+{
+        return self->cached_sunrise;
+}
+
+gdouble
+gsd_natural_light_get_sunset (GsdNaturalLight *self)
+{
+        return self->cached_sunset;
+}
+
+gdouble
+gsd_natural_light_get_temperature (GsdNaturalLight *self)
+{
+        return self->cached_temperature;
+}
+
+void
+gsd_natural_light_set_geoclue_enabled (GsdNaturalLight *self, gboolean enabled)
+{
+        self->geoclue_enabled = enabled;
+}
+
+gboolean
+gsd_natural_light_start (GsdNaturalLight *self, GError **error)
+{
+
+        if (self->geoclue_enabled)
+                register_geoclue (self);
+
+        natural_light_recheck (self);
+        poll_timeout_create (self);
+
+        /* care about changes */
+        g_signal_connect (self->settings, "changed",
+                          G_CALLBACK (settings_changed_cb), self);
+        return TRUE;
+}
+
+static void
+gsd_natural_light_finalize (GObject *object)
+{
+        GsdNaturalLight *self = GSD_NATURAL_LIGHT (object);
+
+        poll_timeout_destroy (self);
+
+        g_clear_object (&self->settings);
+        g_clear_pointer (&self->datetime_override, (GDestroyNotify) g_date_time_unref);
+
+        if (self->cancellable != NULL) {
+                g_cancellable_cancel (self->cancellable);
+                g_clear_object (&self->cancellable);
+        }
+
+        if (self->natural_light_validate_id > 0) {
+                g_source_remove (self->natural_light_validate_id);
+                self->natural_light_validate_id = 0;
+        }
+
+        if (self->geoclue_client != NULL)
+                gclue_client_call_stop (self->geoclue_client, NULL, NULL, NULL);
+        g_clear_object (&self->geoclue_simple);
+        G_OBJECT_CLASS (gsd_natural_light_parent_class)->finalize (object);
+}
+
+static void
+gsd_natural_light_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+        GsdNaturalLight *self = GSD_NATURAL_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:
+                self->disabled_until_tmw = g_value_get_boolean (value);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        }
+}
+
+static void
+gsd_natural_light_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+        GsdNaturalLight *self = GSD_NATURAL_LIGHT (object);
+
+        switch (prop_id) {
+        case PROP_SUNRISE:
+                g_value_set_double (value, self->cached_sunrise);
+                break;
+        case PROP_SUNSET:
+                g_value_set_double (value, self->cached_sunrise);
+                break;
+        case PROP_TEMPERATURE:
+                g_value_set_double (value, self->cached_sunrise);
+                break;
+        case PROP_DISABLED_UNTIL_TMW:
+                g_value_set_boolean (value, gsd_natural_light_get_disabled_until_tmw (self));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        }
+}
+
+static void
+gsd_natural_light_class_init (GsdNaturalLightClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+        object_class->finalize = gsd_natural_light_finalize;
+
+        object_class->set_property = gsd_natural_light_set_property;
+        object_class->get_property = gsd_natural_light_get_property;
+
+        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 natural light is disabled until the 
next day",
+                                                               FALSE,
+                                                               G_PARAM_READWRITE));
+
+}
+
+static void
+gsd_natural_light_init (GsdNaturalLight *self)
+{
+        self->cached_sunrise = -1.f;
+        self->cached_sunset = -1.f;
+        self->cached_temperature = GSD_COLOR_TEMPERATURE_DEFAULT;
+        self->cancellable = g_cancellable_new ();
+        self->settings = g_settings_new ("org.gnome.settings-daemon.plugins.color");
+}
+
+GsdNaturalLight *
+gsd_natural_light_new (void)
+{
+        return g_object_new (GSD_TYPE_NATURAL_LIGHT, NULL);
+}
diff --git a/plugins/color/gsd-natural-light.h b/plugins/color/gsd-natural-light.h
new file mode 100644
index 0000000..8f5a295
--- /dev/null
+++ b/plugins/color/gsd-natural-light.h
@@ -0,0 +1,50 @@
+/*
+ * 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_NATURAL_LIGHT_H__
+#define __GSD_NATURAL_LIGHT_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_NATURAL_LIGHT (gsd_natural_light_get_type ())
+G_DECLARE_FINAL_TYPE (GsdNaturalLight, gsd_natural_light, GSD, NATURAL_LIGHT, GObject)
+
+GsdNaturalLight *gsd_natural_light_new                    (void);
+gboolean         gsd_natural_light_start                  (GsdNaturalLight *self,
+                                                           GError         **error);
+gdouble          gsd_natural_light_get_sunrise            (GsdNaturalLight *self);
+gdouble          gsd_natural_light_get_sunset             (GsdNaturalLight *self);
+gdouble          gsd_natural_light_get_temperature        (GsdNaturalLight *self);
+
+gboolean         gsd_natural_light_get_disabled_until_tmw (GsdNaturalLight *self);
+void             gsd_natural_light_set_disabled_until_tmw (GsdNaturalLight *self,
+                                                           gboolean         value);
+
+/* only for the self test program */
+void             gsd_natural_light_set_geoclue_enabled    (GsdNaturalLight *self,
+                                                           gboolean         enabled);
+void             gsd_natural_light_set_date_time_now      (GsdNaturalLight *self,
+                                                           GDateTime       *datetime);
+
+G_END_DECLS
+
+#endif



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]