=== modified file 'CMakeLists.txt'
--- CMakeLists.txt	2014-02-10 22:59:02 +0000
+++ CMakeLists.txt	2014-02-19 15:36:23 +0000
@@ -38,10 +38,10 @@
                    libical>=0.48
                    libecal-1.2>=3.5
                    libedataserver-1.2>=3.5
+                   libcanberra>=0.12
                    libnotify>=0.7.6
                    url-dispatcher-1>=1
-                   properties-cpp>=0.0.1
-                   json-glib-1.0>=0.16.2)
+                   properties-cpp>=0.0.1)
 include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})
 
 ##

=== modified file 'debian/control'
--- debian/control	2014-02-11 20:06:28 +0000
+++ debian/control	2014-02-19 15:36:23 +0000
@@ -16,13 +16,13 @@
                libgtest-dev,
                libglib2.0-dev (>= 2.35.4),
                libnotify-dev (>= 0.7.6),
+               libcanberra-dev,
                libido3-0.1-dev (>= 0.2.90),
                libgeoclue-dev (>= 0.12.0),
                libecal1.2-dev (>= 3.5),
                libical-dev (>= 1.0),
                libgtk-3-dev (>= 3.1.4),
                libcairo2-dev (>= 1.10),
-               libjson-glib-dev,
                libpolkit-gobject-1-dev,
                libedataserver1.2-dev (>= 3.5),
                libgconf2-dev (>= 2.31),

=== added file 'include/datetime/clock-watcher.h'
--- include/datetime/clock-watcher.h	1970-01-01 00:00:00 +0000
+++ include/datetime/clock-watcher.h	2014-02-19 15:36:23 +0000
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.
+ *
+ * Authors:
+ *   Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_CLOCK_WATCHER_H
+#define INDICATOR_DATETIME_CLOCK_WATCHER_H
+
+#include <datetime/state.h>
+#include <datetime/appointment.h>
+
+#include <core/signal.h>
+
+#include <memory>
+#include <set>
+#include <string>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+
+/**
+ * \brief Watches the clock and appointments to notify when an
+ *        appointment's time is reached.
+ */
+class ClockWatcher
+{
+public:
+    ClockWatcher() =default;
+    virtual ~ClockWatcher() =default;
+    virtual core::Signal<const Appointment&>& alarm_reached() = 0;
+};
+
+
+/**
+ * \brief A #ClockWatcher implementation 
+ */
+class ClockWatcherImpl: public ClockWatcher
+{
+public:
+    ClockWatcherImpl(const std::shared_ptr<const State>& state);
+    ~ClockWatcherImpl() =default;
+    core::Signal<const Appointment&>& alarm_reached();
+
+private:
+    void pulse();
+    std::set<std::string> m_triggered;
+    std::shared_ptr<const State> m_state;
+    core::Signal<const Appointment&> m_alarm_reached;
+};
+
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_CLOCK_WATCHER_H

=== modified file 'include/datetime/date-time.h'
--- include/datetime/date-time.h	2014-01-30 19:00:22 +0000
+++ include/datetime/date-time.h	2014-02-19 15:36:23 +0000
@@ -41,6 +41,7 @@
     DateTime& operator=(GDateTime* in);
     DateTime& operator=(const DateTime& in);
     DateTime to_timezone(const std::string& zone) const;
+    DateTime add_full(int years, int months, int days, int hours, int minutes, double seconds) const;
     void reset(GDateTime* in=nullptr);
 
     GDateTime* get() const;
@@ -48,9 +49,11 @@
 
     std::string format(const std::string& fmt) const;
     int day_of_month() const;
+    double seconds() const;
     int64_t to_unix() const;
 
     bool operator<(const DateTime& that) const;
+    bool operator<=(const DateTime& that) const;
     bool operator!=(const DateTime& that) const;
     bool operator==(const DateTime& that) const;
 

=== modified file 'include/datetime/planner-eds.h'
--- include/datetime/planner-eds.h	2014-01-15 05:07:10 +0000
+++ include/datetime/planner-eds.h	2014-02-19 15:36:23 +0000
@@ -20,9 +20,10 @@
 #ifndef INDICATOR_DATETIME_PLANNER_EDS_H
 #define INDICATOR_DATETIME_PLANNER_EDS_H
 
+#include <datetime/clock.h>
 #include <datetime/planner.h>
 
-#include <memory> // unique_ptr
+#include <memory> // shared_ptr, unique_ptr
 
 namespace unity {
 namespace indicator {
@@ -34,7 +35,7 @@
 class PlannerEds: public Planner
 {
 public:
-    PlannerEds();
+    PlannerEds(const std::shared_ptr<Clock>& clock);
     virtual ~PlannerEds();
 
 private:

=== added file 'include/datetime/snap.h'
--- include/datetime/snap.h	1970-01-01 00:00:00 +0000
+++ include/datetime/snap.h	2014-02-19 15:36:23 +0000
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.
+ *
+ * Authors:
+ *   Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_SNAP_H
+#define INDICATOR_DATETIME_SNAP_H
+
+#include <datetime/appointment.h>
+
+#include <memory>
+#include <functional>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/**
+ * \brief Pops up Snap Decisions for appointments
+ */
+class Snap
+{
+public:
+    Snap();
+    virtual ~Snap();
+
+    typedef std::function<void(const Appointment&)> appointment_func;
+    void operator()(const Appointment& appointment,
+                    appointment_func show,
+                    appointment_func dismiss);
+};
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_SNAP_H

=== modified file 'include/datetime/timezone-file.h'
--- include/datetime/timezone-file.h	2014-01-30 19:00:22 +0000
+++ include/datetime/timezone-file.h	2014-02-19 15:36:23 +0000
@@ -42,8 +42,8 @@
     ~FileTimezone();
 
 private:
-    void setFilename(const std::string& filename);
-    static void onFileChanged(gpointer gself);
+    void set_filename(const std::string& filename);
+    static void on_file_changed(gpointer gself);
     void clear();
     void reload();
 

=== modified file 'po/POTFILES.in'
--- po/POTFILES.in	2014-02-10 22:59:02 +0000
+++ po/POTFILES.in	2014-02-19 15:36:23 +0000
@@ -1,3 +1,5 @@
 src/formatter.cpp
 src/formatter-desktop.cpp
 src/menu.cpp
+src/snap.cpp
+src/utils.c

=== modified file 'src/CMakeLists.txt'
--- src/CMakeLists.txt	2014-02-10 22:59:02 +0000
+++ src/CMakeLists.txt	2014-02-19 15:36:23 +0000
@@ -13,6 +13,7 @@
              appointment.cpp
              clock.cpp
              clock-live.cpp
+             clock-watcher.cpp
              date-time.cpp
              exporter.cpp
              formatter.cpp
@@ -22,6 +23,7 @@
              menu.cpp
              planner-eds.cpp
              settings-live.cpp
+             snap.cpp
              timezone-file.cpp
              timezone-geoclue.cpp
              timezones-live.cpp

=== modified file 'src/actions-live.cpp'
--- src/actions-live.cpp	2014-02-10 22:59:02 +0000
+++ src/actions-live.cpp	2014-02-19 15:36:23 +0000
@@ -156,7 +156,7 @@
 
   GError * err = nullptr;
   auto proxy = g_dbus_proxy_new_for_bus_finish(res, &err);
-  if (err != NULL)
+  if (err != nullptr)
     {
       if (!g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED))
         g_warning("Could not grab DBus proxy for timedated: %s", err->message);

=== added file 'src/clock-watcher.cpp'
--- src/clock-watcher.cpp	1970-01-01 00:00:00 +0000
+++ src/clock-watcher.cpp	2014-02-19 15:36:23 +0000
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.
+ *
+ * Authors:
+ *   Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/clock-watcher.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+ClockWatcherImpl::ClockWatcherImpl(const std::shared_ptr<const State>& state):
+    m_state(state)
+{
+    m_state->planner->upcoming.changed().connect([this](const std::vector<Appointment>&){
+        g_debug("ClockWatcher pulse because upcoming appointments changed");
+        pulse();
+    });
+    m_state->clock->minute_changed.connect([this](){
+        g_debug("ClockWatcher pulse because clock minute_changed");
+        pulse();
+    });
+    pulse();
+}
+
+core::Signal<const Appointment&>& ClockWatcherImpl::alarm_reached()
+{
+    return m_alarm_reached;
+}
+
+void ClockWatcherImpl::pulse()
+{
+    const auto now = m_state->clock->localtime();
+
+    for(const auto& appointment : m_state->planner->upcoming.get())
+    {
+        if (m_triggered.count(appointment.uid))
+            continue;
+        if (!DateTime::is_same_minute(now, appointment.begin))
+            continue;
+
+        m_triggered.insert(appointment.uid);
+        m_alarm_reached(appointment);
+    }
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity

=== modified file 'src/date-time.cpp'
--- src/date-time.cpp	2014-01-30 19:00:22 +0000
+++ src/date-time.cpp	2014-02-19 15:36:23 +0000
@@ -69,6 +69,14 @@
     return dt;
 }
 
+DateTime DateTime::add_full(int years, int months, int days, int hours, int minutes, double seconds) const
+{
+    auto gdt = g_date_time_add_full(get(), years, months, days, hours, minutes, seconds);
+    DateTime dt(gdt);
+    g_date_time_unref(gdt);
+    return dt;
+}
+
 GDateTime* DateTime::get() const
 {
     g_assert(m_dt);
@@ -88,6 +96,11 @@
     return g_date_time_get_day_of_month(get());
 }
 
+double DateTime::seconds() const
+{
+    return g_date_time_get_seconds(get());
+}
+
 int64_t DateTime::to_unix() const
 {
     return g_date_time_to_unix(get());
@@ -112,6 +125,11 @@
     return g_date_time_compare(get(), that.get()) < 0;
 }
 
+bool DateTime::operator<=(const DateTime& that) const
+{
+    return g_date_time_compare(get(), that.get()) <= 0;
+}
+
 bool DateTime::operator!=(const DateTime& that) const
 {
     // return true if this isn't set, or if it's not equal

=== modified file 'src/main.cpp'
--- src/main.cpp	2014-01-30 19:06:33 +0000
+++ src/main.cpp	2014-02-19 15:36:23 +0000
@@ -17,24 +17,25 @@
  * with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
-
 #include <datetime/actions-live.h>
 #include <datetime/clock.h>
+#include <datetime/clock-watcher.h>
 #include <datetime/exporter.h>
 #include <datetime/locations-settings.h>
 #include <datetime/menu.h>
 #include <datetime/planner-eds.h>
 #include <datetime/settings-live.h>
+#include <datetime/snap.h>
 #include <datetime/state.h>
 #include <datetime/timezones-live.h>
 
 #include <glib/gi18n.h> // bindtextdomain()
 #include <gio/gio.h>
-#include <libnotify/notify.h> 
+
+#include <url-dispatcher.h>
 
 #include <locale.h>
-#include <stdlib.h> // exit()
+#include <cstdlib> // exit()
 
 using namespace unity::indicator::datetime;
 
@@ -50,10 +51,6 @@
     bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR);
     textdomain(GETTEXT_PACKAGE);
 
-    // init libnotify
-    if(!notify_init("indicator-datetime-service"))
-        g_critical("libnotify initialization failed");
-
     // build the state, actions, and menufactory
     std::shared_ptr<State> state(new State);
     std::shared_ptr<Settings> live_settings(new LiveSettings);
@@ -62,11 +59,27 @@
     state->settings = live_settings;
     state->clock = live_clock;
     state->locations.reset(new SettingsLocations(live_settings, live_timezones));
-    state->planner.reset(new PlannerEds);
+    state->planner.reset(new PlannerEds(live_clock));
     state->planner->time = live_clock->localtime();
     std::shared_ptr<Actions> actions(new LiveActions(state));
     MenuFactory factory(actions, state);
 
+    // snap decisions
+    ClockWatcherImpl clock_watcher(state);
+    Snap snap;
+    clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){
+        auto snap_show = [](const Appointment& a){
+            const char* url;
+            if(!a.url.empty())
+                url = a.url.c_str();
+            else // alarm doesn't have a URl associated with it; use a fallback
+                url = "appid://com.ubuntu.clock/clock/current-user-version";
+            url_dispatch_send(url, nullptr, nullptr);
+        };
+        auto snap_dismiss = [](const Appointment&){};
+        snap(appt, snap_show, snap_dismiss);
+    });
+
     // create the menus
     std::vector<std::shared_ptr<Menu>> menus;
     for(int i=0, n=Menu::NUM_PROFILES; i<n; i++)

=== modified file 'src/menu.cpp'
--- src/menu.cpp	2014-02-05 18:22:42 +0000
+++ src/menu.cpp	2014-02-19 15:36:23 +0000
@@ -22,11 +22,11 @@
 #include <datetime/formatter.h>
 #include <datetime/state.h>
 
-#include <json-glib/json-glib.h>
-
 #include <glib/gi18n.h>
 #include <gio/gio.h>
 
+#include <vector>
+
 namespace unity {
 namespace indicator {
 namespace datetime {
@@ -62,7 +62,7 @@
 ****/
 
 
-#define FALLBACK_ALARM_CLOCK_ICON_NAME "clock"
+#define ALARM_ICON_NAME "alarm-clock"
 #define CALENDAR_ICON_NAME "calendar"
 
 class MenuImpl: public Menu
@@ -105,12 +105,15 @@
             update_section(Appointments); // showing events got toggled
         });
         m_state->planner->upcoming.changed().connect([this](const std::vector<Appointment>&){
-            update_section(Appointments); // "upcoming" is the list of Appointments we show
+            update_upcoming(); // our m_upcoming is planner->upcoming() filtered by time
         });
         m_state->clock->date_changed.connect([this](){
             update_section(Calendar); // need to update the Date menuitem
             update_section(Locations); // locations' relative time may have changed
         });
+        m_state->clock->minute_changed.connect([this](){
+            update_upcoming(); // our m_upcoming is planner->upcoming() filtered by time
+        });
         m_state->locations->locations.changed().connect([this](const std::vector<Location>&) {
             update_section(Locations); // "locations" is the list of Locations we show
         });
@@ -133,6 +136,24 @@
         g_action_group_change_action_state(action_group, action_name.c_str(), state);
     }
 
+    void update_upcoming()
+    {
+        const auto now = m_state->clock->localtime();
+        const auto next_minute = now.add_full(0,0,0,0,1,-now.seconds());
+
+        std::vector<Appointment> upcoming;
+        for(const auto& a : m_state->planner->upcoming.get())
+            if (next_minute <= a.begin)
+                upcoming.push_back(a);
+ 
+        if (m_upcoming != upcoming)
+        {
+            m_upcoming.swap(upcoming);
+            update_header(); // show an 'alarm' icon if there are upcoming alarms
+            update_section(Appointments); // "upcoming" is the list of Appointments we show
+        }
+    }
+
     std::shared_ptr<const State> m_state;
     std::shared_ptr<Actions> m_actions;
     std::shared_ptr<const Formatter> m_formatter;
@@ -141,71 +162,19 @@
     GVariant* get_serialized_alarm_icon()
     {
         if (G_UNLIKELY(m_serialized_alarm_icon == nullptr))
-            m_serialized_alarm_icon = create_alarm_icon();
+        {
+            auto i = g_themed_icon_new_with_default_fallbacks(ALARM_ICON_NAME);
+            m_serialized_alarm_icon = g_icon_serialize(i);
+            g_object_unref(i);
+        }
 
         return m_serialized_alarm_icon;
     }
 
+    std::vector<Appointment> m_upcoming;
+
 private:
 
-    /* try to get the clock app's filename from click. (/$pkgdir/$icon) */
-    static GVariant* create_alarm_icon()
-    {
-        GVariant* serialized = nullptr;
-        gchar* icon_filename = nullptr;
-        gchar* standard_error = nullptr;
-        gchar* pkgdir = nullptr;
-
-        g_spawn_command_line_sync("click pkgdir com.ubuntu.clock", &pkgdir, &standard_error, nullptr, nullptr);
-        g_clear_pointer(&standard_error, g_free);
-        if (pkgdir != nullptr)
-        {
-            gchar* manifest = nullptr;
-            g_strstrip(pkgdir);
-            g_spawn_command_line_sync("click info com.ubuntu.clock", &manifest, &standard_error, nullptr, nullptr);
-            g_clear_pointer(&standard_error, g_free);
-            if (manifest != nullptr)
-            {
-                JsonParser* parser = json_parser_new();
-                if (json_parser_load_from_data(parser, manifest, -1, nullptr))
-                {
-                    JsonNode* root = json_parser_get_root(parser); /* transfer-none */
-                    if ((root != nullptr) && (JSON_NODE_TYPE(root) == JSON_NODE_OBJECT))
-                    {
-                        JsonObject* o = json_node_get_object(root); /* transfer-none */
-                        const gchar* icon_name = json_object_get_string_member(o, "icon");
-                        if (icon_name != nullptr)
-                            icon_filename = g_build_filename(pkgdir, icon_name, nullptr);
-                    }
-                }
-                g_object_unref(parser);
-                g_free(manifest);
-            }
-            g_free(pkgdir);
-        }
-
-        if (icon_filename != nullptr)
-        {
-            GFile* file = g_file_new_for_path(icon_filename);
-            GIcon* icon = g_file_icon_new(file);
-
-            serialized = g_icon_serialize(icon);
-
-            g_object_unref(icon);
-            g_object_unref(file);
-            g_free(icon_filename);
-        }
-
-        if (serialized == nullptr)
-        {
-            auto i = g_themed_icon_new_with_default_fallbacks(FALLBACK_ALARM_CLOCK_ICON_NAME);
-            serialized = g_icon_serialize(i);
-            g_object_unref(i);
-        }
-
-        return serialized;
-    }
-
     GVariant* get_serialized_calendar_icon()
     {
         if (G_UNLIKELY(m_serialized_calendar_icon == nullptr))
@@ -273,7 +242,7 @@
         // add calendar
         if (show_calendar)
         {
-            item = g_menu_item_new ("[calendar]", NULL);
+            item = g_menu_item_new ("[calendar]", nullptr);
             v = g_variant_new_int64(0);
             g_menu_item_set_action_and_target_value (item, "indicator.calendar", v);
             g_menu_item_set_attribute (item, "x-canonical-type",
@@ -296,11 +265,13 @@
         const int MAX_APPTS = 5;
         std::set<std::string> added;
 
-        for (const auto& appt : m_state->planner->upcoming.get())
+        for (const auto& appt : m_upcoming)
         {
+            // don't show too many
             if (n++ >= MAX_APPTS)
                 break;
 
+            // don't show duplicates
             if (added.count(appt.uid))
                 continue;
 
@@ -508,7 +479,7 @@
     {
         // are there alarms?
         bool has_alarms = false;
-        for(const auto& appointment : m_state->planner->upcoming.get())
+        for(const auto& appointment : m_upcoming)
             if((has_alarms = appointment.has_alarms))
                 break;
 

=== modified file 'src/planner-eds.cpp'
--- src/planner-eds.cpp	2014-01-31 00:33:14 +0000
+++ src/planner-eds.cpp	2014-02-19 15:36:23 +0000
@@ -26,6 +26,9 @@
 #include <libecal/libecal.h>
 #include <libedataserver/libedataserver.h>
 
+#include <map>
+#include <set>
+
 namespace unity {
 namespace indicator {
 namespace datetime {
@@ -34,25 +37,28 @@
 *****
 ****/
 
-G_DEFINE_QUARK("source-client", source_client)
-
-
 class PlannerEds::Impl
 {
 public:
 
-    Impl(PlannerEds& owner):
+    Impl(PlannerEds& owner, const std::shared_ptr<Clock>& clock):
         m_owner(owner),
+        m_clock(clock),
         m_cancellable(g_cancellable_new())
     {
         e_source_registry_new(m_cancellable, on_source_registry_ready, this);
 
+        m_clock->minute_changed.connect([this](){
+            g_debug("rebuilding upcoming because the clock's minute_changed");
+            rebuild_soon(UPCOMING);
+        });
+
         m_owner.time.changed().connect([this](const DateTime& dt) {
-            g_debug("planner's datetime property changed to %s; calling rebuildSoon()", dt.format("%F %T").c_str());
-            rebuildSoon();
+            g_debug("planner's datetime property changed to %s; calling rebuild_soon()", dt.format("%F %T").c_str());
+            rebuild_soon(MONTH);
         });
 
-        rebuildSoon();
+        rebuild_soon(ALL);
     }
 
     ~Impl()
@@ -60,6 +66,9 @@
         g_cancellable_cancel(m_cancellable);
         g_clear_object(&m_cancellable);
 
+        while(!m_sources.empty())
+            remove_source(*m_sources.begin());
+
         if (m_rebuild_tag)
             g_source_remove(m_rebuild_tag);
 
@@ -83,26 +92,31 @@
         }
         else
         {
+            g_signal_connect(r, "source-added",    G_CALLBACK(on_source_added),    gself);
+            g_signal_connect(r, "source-removed",  G_CALLBACK(on_source_removed),  gself);
+            g_signal_connect(r, "source-changed",  G_CALLBACK(on_source_changed),  gself);
+            g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), gself);
+            g_signal_connect(r, "source-enabled",  G_CALLBACK(on_source_enabled),  gself);
+
             auto self = static_cast<Impl*>(gself);
-
-            g_signal_connect(r, "source-added",    G_CALLBACK(on_source_added), self);
-            g_signal_connect(r, "source-removed",  G_CALLBACK(on_source_removed), self);
-            g_signal_connect(r, "source-changed",  G_CALLBACK(on_source_changed), self);
-            g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), self);
-            g_signal_connect(r, "source-enabled",  G_CALLBACK(on_source_enabled), self);
-
             self->m_source_registry = r;
-
-            GList* sources = e_source_registry_list_sources(r, E_SOURCE_EXTENSION_CALENDAR);
-            for (auto l=sources; l!=nullptr; l=l->next)
-                on_source_added(r, E_SOURCE(l->data), gself);
-            g_list_free_full(sources, g_object_unref);
+            self->add_sources_by_extension(E_SOURCE_EXTENSION_CALENDAR);
+            self->add_sources_by_extension(E_SOURCE_EXTENSION_TASK_LIST);
         }
     }
 
+    void add_sources_by_extension(const char* extension)
+    {
+        auto& r = m_source_registry;
+        auto sources = e_source_registry_list_sources(r, extension);
+        for (auto l=sources; l!=nullptr; l=l->next)
+            on_source_added(r, E_SOURCE(l->data), this);
+        g_list_free_full(sources, g_object_unref);
+    }
+
     static void on_source_added(ESourceRegistry* registry, ESource* source, gpointer gself)
     {
-        auto self = static_cast<PlannerEds::Impl*>(gself);
+        auto self = static_cast<Impl*>(gself);
 
         self->m_sources.insert(E_SOURCE(g_object_ref(source)));
 
@@ -112,10 +126,19 @@
 
     static void on_source_enabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
     {
-        auto self = static_cast<PlannerEds::Impl*>(gself);
-
+        auto self = static_cast<Impl*>(gself);
+        ECalClientSourceType source_type;
+
+        if (e_source_has_extension(source, E_SOURCE_EXTENSION_CALENDAR))
+            source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
+        else if (e_source_has_extension(source, E_SOURCE_EXTENSION_TASK_LIST))
+            source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
+        else
+            g_assert_not_reached();
+
+        g_debug("connecting a client to source %s", e_source_get_uid(source));
         e_cal_client_connect(source,
-                             E_CAL_CLIENT_SOURCE_TYPE_EVENTS,
+                             source_type,
                              self->m_cancellable,
                              on_client_connected,
                              gself);
@@ -134,45 +157,118 @@
         }
         else
         {
-            // we've got a new connected ECalClient, so store it & notify clients
-            g_object_set_qdata_full(G_OBJECT(e_client_get_source(client)),
-                                    source_client_quark(),
-                                    client,
-                                    g_object_unref);
-
-            g_debug("client connected; calling rebuildSoon()");
-            static_cast<Impl*>(gself)->rebuildSoon();
-        }
+            // add the client to our collection
+            auto self = static_cast<Impl*>(gself);
+            g_debug("got a client for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client)));
+            self->m_clients[e_client_get_source(client)] = E_CAL_CLIENT(client);
+
+            // now create a view for it so that we can listen for changes
+            e_cal_client_get_view (E_CAL_CLIENT(client),
+                                   "#t", // match all
+                                   self->m_cancellable,
+                                   on_client_view_ready,
+                                   self);
+
+            g_debug("client connected; calling rebuild_soon()");
+            self->rebuild_soon(ALL);
+        }
+    }
+
+    static void on_client_view_ready (GObject* client, GAsyncResult* res, gpointer gself)
+    {
+        GError* error = nullptr;
+        ECalClientView* view = nullptr;
+
+        if (e_cal_client_get_view_finish (E_CAL_CLIENT(client), res, &view, &error))
+        {
+            // add the view to our collection
+            e_cal_client_view_start(view, &error);
+            g_debug("got a view for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client)));
+            auto self = static_cast<Impl*>(gself);
+            self->m_views[e_client_get_source(E_CLIENT(client))] = view;
+
+            g_signal_connect(view, "objects-added", G_CALLBACK(on_view_objects_added), self);
+            g_signal_connect(view, "objects-modified", G_CALLBACK(on_view_objects_modified), self);
+            g_signal_connect(view, "objects-removed", G_CALLBACK(on_view_objects_removed), self);
+            g_debug("view connected; calling rebuild_soon()");
+            self->rebuild_soon(ALL);
+        }
+        else if(error != nullptr)
+        {
+            if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                g_warning("indicator-datetime cannot get View to EDS client: %s", error->message);
+
+            g_error_free(error);
+        }
+    }
+
+    static void on_view_objects_added(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
+    {
+        g_debug("%s", G_STRFUNC);
+        static_cast<Impl*>(gself)->rebuild_soon(ALL);
+    }
+    static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
+    {
+        g_debug("%s", G_STRFUNC);
+        static_cast<Impl*>(gself)->rebuild_soon(ALL);
+    }
+    static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
+    {
+        g_debug("%s", G_STRFUNC);
+        static_cast<Impl*>(gself)->rebuild_soon(ALL);
     }
 
     static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
     {
-        gpointer e_cal_client;
-
-        // if this source has a connected ECalClient, remove it & notify clients
-        if ((e_cal_client = g_object_steal_qdata(G_OBJECT(source), source_client_quark())))
-        {
-            g_object_unref(e_cal_client);
-
-            g_debug("source disabled; calling rebuildSoon()");
-            static_cast<Impl*>(gself)->rebuildSoon();
-        }
-    }
-
-    static void on_source_removed(ESourceRegistry* registry, ESource* source, gpointer gself)
-    {
-        auto self = static_cast<PlannerEds::Impl*>(gself);
-
-        on_source_disabled(registry, source, gself);
-
-        self->m_sources.erase(source);
-        g_object_unref(source);
+        static_cast<Impl*>(gself)->disable_source(source);
+    }
+    void disable_source(ESource* source)
+    {
+        // if an ECalClientView is associated with this source, remove it
+        auto vit = m_views.find(source);
+        if (vit != m_views.end())
+        {
+            auto& view = vit->second;
+            e_cal_client_view_stop(view, nullptr);
+            const auto n_disconnected = g_signal_handlers_disconnect_by_data(view, this);
+            g_warn_if_fail(n_disconnected == 3);
+            g_object_unref(view);
+            m_views.erase(vit);
+            rebuild_soon(ALL);
+        }
+
+        // if an ECalClient is associated with this source, remove it
+        auto cit = m_clients.find(source);
+        if (cit != m_clients.end())
+        {
+            auto& client = cit->second;
+            g_object_unref(client);
+            m_clients.erase(cit);
+            rebuild_soon(ALL);
+        }
+    }
+
+    static void on_source_removed(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
+    {
+        static_cast<Impl*>(gself)->remove_source(source);
+    }
+    void remove_source(ESource* source)
+    {
+        disable_source(source);
+
+        auto sit = m_sources.find(source);
+        if (sit != m_sources.end())
+        {
+            g_object_unref(*sit);
+            m_sources.erase(sit);
+            rebuild_soon(ALL);
+        }
     }
 
     static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself)
     {
-        g_debug("source changed; calling rebuildSoon()");
-        static_cast<Impl*>(gself)->rebuildSoon();
+        g_debug("source changed; calling rebuild_soon()");
+        static_cast<Impl*>(gself)->rebuild_soon(ALL);
     }
 
 private:
@@ -196,58 +292,72 @@
             task(task_in), client(client_in), color(color_in) {}
     };
 
-    void rebuildSoon()
+    void rebuild_soon(int rebuild_flags)
     {
-        const static guint ARBITRARY_INTERVAL_SECS = 2;
+        static const guint ARBITRARY_INTERVAL_SECS = 2;
+
+        m_rebuild_flags |= rebuild_flags;
 
         if (m_rebuild_tag == 0)
-            m_rebuild_tag = g_timeout_add_seconds(ARBITRARY_INTERVAL_SECS, rebuildNowStatic, this);
+            m_rebuild_tag = g_timeout_add_seconds(ARBITRARY_INTERVAL_SECS, rebuild_now_static, this);
     }
 
-    static gboolean rebuildNowStatic(gpointer gself)
+    static gboolean rebuild_now_static(gpointer gself)
     {
         auto self = static_cast<Impl*>(gself);
+        const auto flags = self->m_rebuild_flags;
         self->m_rebuild_tag = 0;
-        self->rebuildNow();
+        self->m_rebuild_flags = 0;
+        self->rebuild_now(flags);
         return G_SOURCE_REMOVE;
     }
 
-    void rebuildNow()
-    {
-        const auto calendar_date = m_owner.time.get().get();
-        GDateTime* begin;
-        GDateTime* end;
-        int y, m, d;
-
-        // get all the appointments in the calendar month
-        g_date_time_get_ymd(calendar_date, &y, &m, &d);
-        begin = g_date_time_new_local(y, m, 1, 0, 0, 0.1);
-        end = g_date_time_new_local(y, m, g_date_get_days_in_month(GDateMonth(m),GDateYear(y)), 23, 59, 59.9);
-        if (begin && end)
-        {
-            getAppointments(begin, end, [this](const std::vector<Appointment>& appointments) {
-                g_debug("got %d appointments in this calendar month", (int)appointments.size());
-                m_owner.this_month.set(appointments);
-            });
-        }
-        g_clear_pointer(&begin, g_date_time_unref);
-        g_clear_pointer(&end, g_date_time_unref);
-
-        // get the upcoming appointments
-        begin = g_date_time_ref(calendar_date);
-        end = g_date_time_add_months(begin, 1);
-        if (begin && end)
-        {
-            getAppointments(begin, end, [this](const std::vector<Appointment>& appointments) {
-                g_debug("got %d upcoming appointments", (int)appointments.size());
-                m_owner.upcoming.set(appointments);
-            });
-        }
-        g_clear_pointer(&begin, g_date_time_unref);
-        g_clear_pointer(&end, g_date_time_unref);
-    }
-
-    void getAppointments(GDateTime* begin_dt, GDateTime* end_dt, appointment_func func)
+    void rebuild_now(int rebuild_flags)
+    {
+        if (rebuild_flags & UPCOMING)
+            rebuild_upcoming();
+
+        if (rebuild_flags & MONTH)
+            rebuild_month();
+    }
+
+    void rebuild_month()
+    {
+        const auto ref = m_owner.time.get().get();
+        auto month_begin = g_date_time_add_full(ref,
+                                                0, // subtract no years
+                                                0, // subtract no months
+                                                -(g_date_time_get_day_of_month(ref)-1),
+                                                -g_date_time_get_hour(ref),
+                                                -g_date_time_get_minute(ref),
+                                                -g_date_time_get_seconds(ref));
+        auto month_end = g_date_time_add_full(month_begin, 0, 1, 0, 0, 0, -0.1);
+
+        get_appointments(month_begin, month_end, [this](const std::vector<Appointment>& appointments) {
+            g_debug("got %d appointments in this calendar month", (int)appointments.size());
+            m_owner.this_month.set(appointments);
+        });
+    
+        g_date_time_unref(month_end);
+        g_date_time_unref(month_begin);
+    }
+
+    void rebuild_upcoming()
+    {
+        const auto ref = m_clock->localtime();
+        const auto begin = g_date_time_add_minutes(ref.get(),-10);
+        const auto end = g_date_time_add_months(begin,1);
+
+        get_appointments(begin, end, [this](const std::vector<Appointment>& appointments) {
+            g_debug("got %d upcoming appointments", (int)appointments.size());
+            m_owner.upcoming.set(appointments);
+        });
+
+        g_date_time_unref(end);
+        g_date_time_unref(begin);
+    }
+
+    void get_appointments(GDateTime* begin_dt, GDateTime* end_dt, appointment_func func)
     {
         const auto begin = g_date_time_to_unix(begin_dt);
         const auto end = g_date_time_to_unix(end_dt);
@@ -286,16 +396,14 @@
             delete task;
         });
 
-        for (auto& source : m_sources)
+        for (auto& kv : m_clients)
         {
-            auto client = E_CAL_CLIENT(g_object_get_qdata(G_OBJECT(source), source_client_quark()));
-            if (client == nullptr)
-                continue;
-
+            auto& client = kv.second;
             if (default_timezone != nullptr)
                 e_cal_client_set_default_timezone(client, default_timezone);
 
             // start a new subtask to enumerate all the components in this client.
+            auto& source = kv.first;
             auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR);
             const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension));
             g_debug("calling e_cal_client_generate_instances for %p", (void*)client);
@@ -407,16 +515,19 @@
         delete subtask;
     }
 
-private:
-
     PlannerEds& m_owner;
+    std::shared_ptr<Clock> m_clock;
     std::set<ESource*> m_sources;
-    GCancellable * m_cancellable = nullptr;
-    ESourceRegistry * m_source_registry = nullptr;
+    std::map<ESource*,ECalClient*> m_clients;
+    std::map<ESource*,ECalClientView*> m_views;
+    GCancellable* m_cancellable = nullptr;
+    ESourceRegistry* m_source_registry = nullptr;
     guint m_rebuild_tag = 0;
+    guint m_rebuild_flags = 0;
+    enum { UPCOMING=(1<<0), MONTH=(1<<1), ALL=UPCOMING|MONTH };
 };
 
-PlannerEds::PlannerEds(): p(new Impl(*this)) {}
+PlannerEds::PlannerEds(const std::shared_ptr<Clock>& clock): p(new Impl(*this, clock)) {}
 
 PlannerEds::~PlannerEds() =default;
 

=== added file 'src/snap.cpp'
--- src/snap.cpp	1970-01-01 00:00:00 +0000
+++ src/snap.cpp	2014-02-19 15:36:23 +0000
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.
+ *
+ * Authors:
+ *   Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/appointment.h>
+#include <datetime/formatter.h>
+#include <datetime/snap.h>
+
+#include <canberra.h>
+#include <libnotify/notify.h>
+
+#include <glib/gi18n.h>
+#include <glib.h>
+
+#define ALARM_SOUND_FILENAME "/usr/share/sounds/ubuntu/stereo/phone-incoming-call.ogg"
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+namespace
+{
+
+/** 
+***  libcanberra -- play sounds
+**/
+
+// arbitrary number, but we need a consistent id for play/cancel
+const int32_t alarm_ca_id = 1;
+
+gboolean media_cached = FALSE;
+ca_context *c_context = nullptr;
+guint timeout_tag = 0;
+
+ca_context* get_ca_context()
+{
+    if (G_UNLIKELY(c_context == nullptr))
+    {
+        int rv;
+
+        if ((rv = ca_context_create(&c_context)) != CA_SUCCESS)
+        {
+            g_warning("Failed to create canberra context: %s\n", ca_strerror(rv));
+            c_context = nullptr;
+        }
+        else
+        {
+            const char* filename = ALARM_SOUND_FILENAME;
+            rv = ca_context_cache(c_context,
+                                  CA_PROP_EVENT_ID, "alarm",
+                                  CA_PROP_MEDIA_FILENAME, filename,
+                                  CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
+                                  NULL);
+            media_cached = rv == CA_SUCCESS;
+            if (rv != CA_SUCCESS)
+                g_warning("Couldn't add '%s' to canberra cache: %s", filename, ca_strerror(rv));
+        }
+    }
+
+    return c_context;
+}
+
+void play_alarm_sound();
+
+gboolean play_alarm_sound_idle (gpointer)
+{
+    timeout_tag = 0;
+    play_alarm_sound();
+    return G_SOURCE_REMOVE;
+}
+
+void on_alarm_play_done (ca_context* /*context*/, uint32_t /*id*/, int rv, void* /*user_data*/)
+{
+    // wait one second, then play it again
+    if ((rv == CA_SUCCESS) && (timeout_tag == 0))
+        timeout_tag = g_timeout_add_seconds (1, play_alarm_sound_idle, nullptr);
+}
+
+void play_alarm_sound()
+{
+    const gchar* filename = ALARM_SOUND_FILENAME;
+    auto context = get_ca_context();
+    g_return_if_fail(context != nullptr);
+
+    ca_proplist* props = nullptr;
+    ca_proplist_create(&props);
+    if (media_cached)
+        ca_proplist_sets(props, CA_PROP_EVENT_ID, "alarm");
+    ca_proplist_sets(props, CA_PROP_MEDIA_FILENAME, filename);
+
+    const auto rv = ca_context_play_full(context, alarm_ca_id, props, on_alarm_play_done, nullptr);
+    if (rv != CA_SUCCESS)
+        g_warning("Failed to play file '%s': %s", filename, ca_strerror(rv));
+
+    g_clear_pointer(&props, ca_proplist_destroy);
+}
+
+void stop_alarm_sound()
+{
+    auto context = get_ca_context();
+    if (context != nullptr)
+    {
+        const auto rv = ca_context_cancel(context, alarm_ca_id);
+        if (rv != CA_SUCCESS)
+            g_warning("Failed to cancel alarm sound: %s", ca_strerror(rv));
+    }
+
+    if (timeout_tag != 0)
+    {
+        g_source_remove(timeout_tag);
+        timeout_tag = 0;
+    }
+}
+
+/** 
+***  libnotify -- snap decisions
+**/
+
+void first_time_init()
+{
+    static bool inited = false;
+
+    if (G_UNLIKELY(!inited))
+    {
+        inited = true;
+
+        if(!notify_init("indicator-datetime-service"))
+            g_critical("libnotify initialization failed");
+    }
+}
+
+struct SnapData
+{
+    Snap::appointment_func show;
+    Snap::appointment_func dismiss;
+    Appointment appointment;
+};
+
+void on_snap_show(NotifyNotification*, gchar* /*action*/, gpointer gdata)
+{
+    stop_alarm_sound();
+    auto data = static_cast<SnapData*>(gdata);
+    data->show(data->appointment);
+}
+
+void on_snap_dismiss(NotifyNotification*, gchar* /*action*/, gpointer gdata)
+{
+    stop_alarm_sound();
+    auto data = static_cast<SnapData*>(gdata);
+    data->dismiss(data->appointment);
+}
+
+void snap_data_destroy_notify(gpointer gdata)
+{
+     delete static_cast<SnapData*>(gdata);
+}
+
+void show_snap_decision(SnapData* data)
+{
+    const Appointment& appointment = data->appointment;
+
+    const auto timestr = appointment.begin.format("%a, %X");
+    auto title = g_strdup_printf(_("Alarm %s"), timestr.c_str());
+    const auto body = appointment.summary;
+    const gchar* icon_name = "alarm-clock";
+
+    auto nn = notify_notification_new(title, body.c_str(), icon_name);
+    notify_notification_set_hint_string(nn, "x-canonical-snap-decisions", "true");
+    notify_notification_set_hint_string(nn, "x-canonical-private-button-tint", "true");
+    notify_notification_add_action(nn, "show", _("Show"), on_snap_show, data, nullptr);
+    notify_notification_add_action(nn, "dismiss", _("Dismiss"), on_snap_dismiss, data, nullptr);
+    g_object_set_data_full(G_OBJECT(nn), "snap-data", data, snap_data_destroy_notify);
+
+    GError * error = nullptr;
+    notify_notification_show(nn, &error);
+    if (error != NULL)
+    {
+        g_warning("Unable to show snap decision for '%s': %s", body.c_str(), error->message);
+        g_error_free(error);
+        data->show(data->appointment);
+    }
+
+    g_free(title);
+}
+
+/** 
+***
+**/
+
+void notify(const Appointment& appointment,
+            Snap::appointment_func show,
+            Snap::appointment_func dismiss)
+{
+    auto data = new SnapData;
+    data->appointment = appointment;
+    data->show = show;
+    data->dismiss = dismiss;
+
+    play_alarm_sound();
+    show_snap_decision(data);
+}
+
+} // unnamed namespace
+
+
+/***
+****
+***/
+
+Snap::Snap()
+{
+    first_time_init();
+}
+
+Snap::~Snap()
+{
+    media_cached = false;
+    g_clear_pointer(&c_context, ca_context_destroy);
+}
+
+void Snap::operator()(const Appointment& appointment,
+                      appointment_func show,
+                      appointment_func dismiss)
+{
+    if (appointment.has_alarms)
+        notify(appointment, show, dismiss);
+    else
+        dismiss(appointment);
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity

=== modified file 'src/timezone-file.cpp'
--- src/timezone-file.cpp	2014-02-19 15:36:23 +0000
+++ src/timezone-file.cpp	2014-02-19 15:36:23 +0000
@@ -32,7 +32,7 @@
 
 FileTimezone::FileTimezone(const std::string& filename)
 {
-    setFilename(filename);
+    set_filename(filename);
 }
 
 FileTimezone::~FileTimezone()
@@ -52,7 +52,7 @@
 }
 
 void
-FileTimezone::setFilename(const std::string& filename)
+FileTimezone::set_filename(const std::string& filename)
 {
     clear();
 
@@ -79,7 +79,7 @@
       }
     else
       {
-        m_monitor_handler_id = g_signal_connect_swapped(m_monitor, "changed", G_CALLBACK(onFileChanged), this);
+        m_monitor_handler_id = g_signal_connect_swapped(m_monitor, "changed", G_CALLBACK(on_file_changed), this);
         g_debug("%s Monitoring timezone file '%s'", G_STRLOC, m_filename.c_str());
       }
 
@@ -87,7 +87,7 @@
 }
 
 void
-FileTimezone::onFileChanged(gpointer gself)
+FileTimezone::on_file_changed(gpointer gself)
 {
     static_cast<FileTimezone*>(gself)->reload();
 }

=== modified file 'tests/CMakeLists.txt'
--- tests/CMakeLists.txt	2014-01-29 03:42:05 +0000
+++ tests/CMakeLists.txt	2014-02-19 15:36:23 +0000
@@ -42,6 +42,7 @@
 endfunction()
 add_test_by_name(test-actions)
 add_test_by_name(test-clock)
+add_test_by_name(test-clock-watcher)
 add_test_by_name(test-exporter)
 add_test_by_name(test-formatter)
 add_test_by_name(test-live-actions)
@@ -52,6 +53,10 @@
 add_test_by_name(test-timezone-file)
 add_test_by_name(test-utils)
 
+set (TEST_NAME manual-test-snap)
+add_executable (${TEST_NAME} ${TEST_NAME}.cpp)
+add_dependencies (${TEST_NAME} libindicatordatetimeservice)
+target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS})
 
 # disabling the timezone unit tests because they require
 # https://code.launchpad.net/~ted/dbus-test-runner/multi-interface-test/+merge/199724

=== modified file 'tests/geoclue-fixture.h'
--- tests/geoclue-fixture.h	2014-01-17 03:23:57 +0000
+++ tests/geoclue-fixture.h	2014-02-19 15:36:23 +0000
@@ -95,7 +95,7 @@
 
       // I've looked and can't find where this extra ref is coming from.
       // is there an unbalanced ref to the bus in the test harness?!
-      while (bus != NULL)
+      while (bus != nullptr)
         {
           g_object_unref (bus);
           wait_msec (1000);

=== added file 'tests/manual-test-snap.cpp'
--- tests/manual-test-snap.cpp	1970-01-01 00:00:00 +0000
+++ tests/manual-test-snap.cpp	2014-02-19 15:36:23 +0000
@@ -0,0 +1,63 @@
+
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Authors:
+ *   Charles Kerr <charles.kerr@canonical.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, 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 <datetime/appointment.h>
+#include <datetime/snap.h>
+
+#include <glib.h>
+
+using namespace unity::indicator::datetime;
+
+/***
+****
+***/
+
+int main()
+{
+    Appointment a;
+    a.color = "green";
+    a.summary = "Alarm";
+    a.url = "alarm:///hello-world";
+    a.uid = "D4B57D50247291478ED31DED17FF0A9838DED402";
+    a.is_event = false;
+    a.is_daily = false;
+    a.has_alarms = true;
+    auto begin = g_date_time_new_local(2014,12,25,0,0,0);
+    auto end = g_date_time_add_full(begin,0,0,1,0,0,-1);
+    a.begin = begin;
+    a.end = end;
+    g_date_time_unref(end);
+    g_date_time_unref(begin);
+
+    auto loop = g_main_loop_new(nullptr, false);
+    auto show = [loop](const Appointment& appt){
+        g_message("You clicked 'show' for appt url '%s'", appt.url.c_str());
+        g_main_loop_quit(loop);
+    };
+    auto dismiss = [loop](const Appointment&){
+        g_message("You clicked 'dismiss'");
+        g_main_loop_quit(loop);
+    };
+    
+    Snap snap;
+    snap(a, show, dismiss);
+    g_main_loop_run(loop);
+    return 0;
+}

=== added file 'tests/test-clock-watcher.cpp'
--- tests/test-clock-watcher.cpp	1970-01-01 00:00:00 +0000
+++ tests/test-clock-watcher.cpp	2014-02-19 15:36:23 +0000
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.
+ *
+ * Authors:
+ *   Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/clock-watcher.h>
+
+#include <gtest/gtest.h>
+
+#include "state-fixture.h"
+
+using namespace unity::indicator::datetime;
+
+class ClockWatcherFixture: public StateFixture
+{
+private:
+
+    typedef StateFixture super;
+
+protected:
+
+    std::vector<std::string> m_triggered;
+    std::unique_ptr<ClockWatcher> m_watcher;
+
+    void SetUp()
+    {
+        super::SetUp();
+
+        m_watcher.reset(new ClockWatcherImpl(m_state));
+        m_watcher->alarm_reached().connect([this](const Appointment& appt){
+            m_triggered.push_back(appt.uid);
+        });
+
+        EXPECT_TRUE(m_triggered.empty());
+    }
+
+    void TearDown()
+    {
+        m_triggered.clear();
+        m_watcher.reset();
+
+        super::TearDown();
+    }
+
+    std::vector<Appointment> build_some_appointments()
+    {
+        const auto now = m_state->clock->localtime();
+        auto tomorrow = g_date_time_add_days (now.get(), 1);
+        auto tomorrow_begin = g_date_time_add_full (tomorrow, 0, 0, 0,
+                                                    -g_date_time_get_hour(tomorrow),
+                                                    -g_date_time_get_minute(tomorrow),
+                                                    -g_date_time_get_seconds(tomorrow));
+        auto tomorrow_end = g_date_time_add_full (tomorrow_begin, 0, 0, 1, 0, 0, -1);
+
+        Appointment a1; // an alarm clock appointment
+        a1.color = "red";
+        a1.summary = "Alarm";
+        a1.summary = "http://www.example.com/";
+        a1.uid = "example";
+        a1.has_alarms = true;
+        a1.begin = tomorrow_begin;
+        a1.end = tomorrow_end;
+
+        auto ubermorgen_begin = g_date_time_add_days (tomorrow, 1);
+        auto ubermorgen_end = g_date_time_add_full (tomorrow_begin, 0, 0, 1, 0, 0, -1);
+
+        Appointment a2; // a non-alarm appointment
+        a2.color = "green";
+        a2.summary = "Other Text";
+        a2.summary = "http://www.monkey.com/";
+        a2.uid = "monkey";
+        a2.has_alarms = false;
+        a2.begin = ubermorgen_begin;
+        a2.end = ubermorgen_end;
+
+        // cleanup
+        g_date_time_unref(ubermorgen_end);
+        g_date_time_unref(ubermorgen_begin);
+        g_date_time_unref(tomorrow_end);
+        g_date_time_unref(tomorrow_begin);
+        g_date_time_unref(tomorrow);
+
+        return std::vector<Appointment>({a1, a2});
+    }
+};
+
+/***
+****
+***/
+
+TEST_F(ClockWatcherFixture, AppointmentsChanged)
+{
+    // Add some appointments to the planner.
+    // One of these matches our state's localtime, so that should get triggered.
+    std::vector<Appointment> a = build_some_appointments();
+    a[0].begin = m_state->clock->localtime();
+    m_state->planner->upcoming.set(a);
+
+    // Confirm that it got fired
+    EXPECT_EQ(1, m_triggered.size());
+    EXPECT_EQ(a[0].uid, m_triggered[0]);
+}
+
+
+TEST_F(ClockWatcherFixture, TimeChanged)
+{
+    // Add some appointments to the planner.
+    // Neither of these match the state's localtime, so nothing should be triggered.
+    std::vector<Appointment> a = build_some_appointments();
+    m_state->planner->upcoming.set(a);
+    EXPECT_TRUE(m_triggered.empty());
+
+    // Set the state's clock to a time that matches one of the appointments.
+    // That appointment should get triggered.
+    m_mock_state->mock_clock->set_localtime(a[1].begin);
+    EXPECT_EQ(1, m_triggered.size());
+    EXPECT_EQ(a[1].uid, m_triggered[0]);
+}
+
+
+TEST_F(ClockWatcherFixture, MoreThanOne)
+{
+    const auto now = m_state->clock->localtime();
+    std::vector<Appointment> a = build_some_appointments();
+    a[0].begin = a[1].begin = now;
+    m_state->planner->upcoming.set(a);
+
+    EXPECT_EQ(2, m_triggered.size());
+    EXPECT_EQ(a[0].uid, m_triggered[0]);
+    EXPECT_EQ(a[1].uid, m_triggered[1]);
+}
+
+
+TEST_F(ClockWatcherFixture, NoDuplicates)
+{
+    // Setup: add an appointment that gets triggered.
+    const auto now = m_state->clock->localtime();
+    const std::vector<Appointment> appointments = build_some_appointments();
+    std::vector<Appointment> a;
+    a.push_back(appointments[0]);
+    a[0].begin = now;
+    m_state->planner->upcoming.set(a);
+    EXPECT_EQ(1, m_triggered.size());
+    EXPECT_EQ(a[0].uid, m_triggered[0]);
+
+    // Now change the appointment vector by adding one to it.
+    // Confirm that the ClockWatcher doesn't re-trigger a[0]
+    a.push_back(appointments[1]);
+    m_state->planner->upcoming.set(a);
+    EXPECT_EQ(1, m_triggered.size());
+    EXPECT_EQ(a[0].uid, m_triggered[0]);
+}

=== modified file 'tests/test-clock.cpp'
--- tests/test-clock.cpp	2014-01-31 00:33:14 +0000
+++ tests/test-clock.cpp	2014-02-19 15:36:23 +0000
@@ -37,12 +37,12 @@
     void emitPrepareForSleep()
     {
       g_dbus_connection_emit_signal(g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr),
-                                    NULL,
+                                    nullptr,
                                     "/org/freedesktop/login1", // object path
                                     "org.freedesktop.login1.Manager", // interface
                                     "PrepareForSleep", // signal name
                                     g_variant_new("(b)", FALSE),
-                                    NULL);
+                                    nullptr);
     }
 };
 

=== modified file 'tests/test-planner.cpp'
--- tests/test-planner.cpp	2014-01-31 00:33:14 +0000
+++ tests/test-planner.cpp	2014-02-19 15:36:23 +0000
@@ -28,9 +28,7 @@
 #include <langinfo.h>
 #include <locale.h>
 
-using unity::indicator::datetime::Appointment;
-using unity::indicator::datetime::DateTime;
-using unity::indicator::datetime::PlannerEds;
+using namespace unity::indicator::datetime;
 
 /***
 ****
@@ -40,11 +38,15 @@
 
 TEST_F(PlannerFixture, EDS)
 {
-    PlannerEds planner;
+    auto tmp = g_date_time_new_now_local();
+    const auto now = DateTime(tmp);
+    g_date_time_unref(tmp);
+
+    std::shared_ptr<Clock> clock(new MockClock(now));
+    PlannerEds planner(clock);
     wait_msec(100);
 
-    auto now = g_date_time_new_now_local();
-    planner.time.set(DateTime(now));
+    planner.time.set(now);
     wait_msec(2500);
 
     std::vector<Appointment> this_month = planner.this_month.get();

=== modified file 'tests/test-settings.cpp'
--- tests/test-settings.cpp	2014-01-31 00:40:13 +0000
+++ tests/test-settings.cpp	2014-02-19 15:36:23 +0000
@@ -167,8 +167,8 @@
 {
     const auto key = SETTINGS_LOCATIONS_S;
 
-    const gchar* astrv[] = {"America/Los_Angeles Oakland", "America/Chicago Oklahoma City", "Europe/London London", NULL};
-    const gchar* bstrv[] = {"America/Denver", "Europe/London London", "Europe/Berlin Berlin", NULL};
+    const gchar* astrv[] = {"America/Los_Angeles Oakland", "America/Chicago Oklahoma City", "Europe/London London", nullptr};
+    const gchar* bstrv[] = {"America/Denver", "Europe/London London", "Europe/Berlin Berlin", nullptr};
     const std::vector<std::string> av = strv_to_vector(astrv);
     const std::vector<std::string> bv = strv_to_vector(bstrv);
     

=== modified file 'tests/test-utils.cpp'
--- tests/test-utils.cpp	2014-01-27 07:26:02 +0000
+++ tests/test-utils.cpp	2014-02-19 15:36:23 +0000
@@ -59,7 +59,7 @@
         const char* location;
         const char* expected_name;
     } beautify_timezone_test_cases[] = {
-        { "America/Chicago", NULL, "Chicago" },
+        { "America/Chicago", nullptr, "Chicago" },
         { "America/Chicago", "America/Chicago", "Chicago" },
         { "America/Chicago", "America/Chigago Chicago", "Chicago" },
         { "America/Chicago", "America/Chicago Oklahoma City", "Oklahoma City" },

