aboutsummaryrefslogtreecommitdiff
path: root/src/service.c
diff options
context:
space:
mode:
authorCharles Kerr <charles.kerr@canonical.com>2013-10-17 03:39:12 +0000
committerTarmac <Unknown>2013-10-17 03:39:12 +0000
commite38b6293bb37557d27efd052c82ee44d70996077 (patch)
treeffcb823fe27b30a448422a7ef72f3727a81d4469 /src/service.c
parent3cb74c566f21cead559308c94f3b3995e35e6fa2 (diff)
parent05c1039f4b9f0014371bb767089755317ea2d322 (diff)
downloadayatana-indicator-datetime-e38b6293bb37557d27efd052c82ee44d70996077.tar.gz
ayatana-indicator-datetime-e38b6293bb37557d27efd052c82ee44d70996077.tar.bz2
ayatana-indicator-datetime-e38b6293bb37557d27efd052c82ee44d70996077.zip
== Changes to planner-eds:
The get-appointments GTask has a new task subtype for pulling an ECalComponent's uris asynchronously. When get_appointments() is called, create one GTask. We add subtasks to it for each client we know of for calling e_cal_client_generate_instances(). What's new is that for each ECalComponent we find in generate_instances(), we add another new subtask that tries to get the uris for that component. == Testing changes: Make "planner" a property in IndicatorDatetimeService so that we can swap in different appointment planners at runtime. This is for unit testing purposes. Add a mechanism for testing snap decisions without an EDS backend. == Service changes: Every time the appointment list changes, walk through it to find the alarm that will occur the soonest. Set a timer to wake up at that time. When the timer is reached, pop up a snap decision for each alarm set to that time. If the user clicks "OK", dispatch the URL associated with that alarm. Made the appointment menuitems clickable, they now dispatch the appointment's URL. Fixes: https://bugs.launchpad.net/bugs/1233176. Approved by PS Jenkins bot, Ted Gould.
Diffstat (limited to 'src/service.c')
-rw-r--r--src/service.c386
1 files changed, 359 insertions, 27 deletions
diff --git a/src/service.c b/src/service.c
index ef987d5..08945bc 100644
--- a/src/service.c
+++ b/src/service.c
@@ -24,11 +24,11 @@
#include <glib/gi18n.h>
#include <gio/gio.h>
+#include <libnotify/notify.h>
#include <json-glib/json-glib.h>
#include <url-dispatcher.h>
#include "dbus-shared.h"
-#include "planner-eds.h"
#include "timezone-file.h"
#include "timezone-geoclue.h"
#include "service.h"
@@ -53,6 +53,15 @@ static guint signals[LAST_SIGNAL] = { 0 };
enum
{
+ PROP_0,
+ PROP_PLANNER,
+ PROP_LAST
+};
+
+static GParamSpec * properties[PROP_LAST] = { 0 };
+
+enum
+{
SECTION_HEADER = (1<<0),
SECTION_CALENDAR = (1<<1),
SECTION_APPOINTMENTS = (1<<2),
@@ -119,6 +128,7 @@ struct _IndicatorDatetimeServicePrivate
guint header_timer;
guint timezone_timer;
+ guint alarm_timer;
/* Which year/month to show in the calendar,
and which day should get the cursor.
@@ -388,6 +398,197 @@ start_header_timer (IndicatorDatetimeService * self)
g_date_time_unref (now);
}
+/***
+**** ALARMS
+***/
+
+static void set_alarm_timer (IndicatorDatetimeService * self);
+
+static gboolean
+appointment_has_alarm_url (const struct IndicatorDatetimeAppt * appt)
+{
+ return (appt->has_alarms) &&
+ (appt->url != NULL) &&
+ (g_str_has_prefix (appt->url, "alarm:///"));
+}
+
+static gboolean
+datetimes_have_the_same_minute (GDateTime * a G_GNUC_UNUSED, GDateTime * b G_GNUC_UNUSED)
+{
+ int ay, am, ad;
+ int by, bm, bd;
+
+ g_date_time_get_ymd (a, &ay, &am, &ad);
+ g_date_time_get_ymd (b, &by, &bm, &bd);
+
+ return (ay == by) &&
+ (am == bm) &&
+ (ad == bd) &&
+ (g_date_time_get_hour (a) == g_date_time_get_hour (b)) &&
+ (g_date_time_get_minute (a) == g_date_time_get_minute (b));
+}
+
+static void
+dispatch_alarm_url (const struct IndicatorDatetimeAppt * appt)
+{
+ gchar * str;
+
+ g_return_if_fail (appt != NULL);
+ g_return_if_fail (appointment_has_alarm_url (appt));
+
+ str = g_date_time_format (appt->begin, "%F %T");
+ g_debug ("dispatching url \"%s\" for appointment \"%s\", which begins at %s",
+ appt->url, appt->summary, str);
+ g_free (str);
+
+ url_dispatch_send (appt->url, NULL, NULL);
+}
+
+static void
+on_snap_decided (NotifyNotification * notification G_GNUC_UNUSED,
+ char * action,
+ gpointer gurl)
+{
+ g_debug ("%s: %s", G_STRFUNC, action);
+
+ if (!g_strcmp0 (action, "show"))
+ {
+ const gchar * url = gurl;
+ g_debug ("dispatching url '%s'", url);
+ url_dispatch_send (url, NULL, NULL);
+ }
+}
+
+static void
+show_snap_decision_for_alarm (const struct IndicatorDatetimeAppt * appt)
+{
+ gchar * title;
+ const gchar * body;
+ const gchar * icon_name;
+ NotifyNotification * nn;
+ GError * error;
+
+ title = g_date_time_format (appt->begin,
+ get_terse_time_format_string (appt->begin));
+ body = appt->summary;
+ icon_name = ALARM_CLOCK_ICON_NAME;
+ g_debug ("creating a snap decision with title '%s', body '%s', icon '%s'",
+ title, body, icon_name);
+
+ nn = notify_notification_new (title, body, 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_decided, g_strdup(appt->url), g_free);
+ notify_notification_add_action (nn, "dismiss", _("Dismiss"),
+ on_snap_decided, NULL, NULL);
+
+ error = NULL;
+ notify_notification_show (nn, &error);
+ if (error != NULL)
+ {
+ g_warning ("Unable to show alarm '%s' popup: %s", body, error->message);
+ g_error_free (error);
+ dispatch_alarm_url (appt);
+ }
+
+ g_free (title);
+}
+
+static void update_appointment_lists (IndicatorDatetimeService * self);
+
+static gboolean
+on_alarm_timer (gpointer gself)
+{
+ IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself);
+ GDateTime * now;
+ GSList * l;
+
+ /* If there are any alarms at the current time, show a snap decision */
+ now = indicator_datetime_service_get_localtime (self);
+ for (l=self->priv->upcoming_appointments; l!=NULL; l=l->next)
+ {
+ const struct IndicatorDatetimeAppt * appt = l->data;
+
+ if (appointment_has_alarm_url (appt))
+ if (datetimes_have_the_same_minute (now, appt->begin))
+ show_snap_decision_for_alarm (appt);
+ }
+ g_date_time_unref (now);
+
+ /* rebuild the alarm list asynchronously.
+ set_upcoming_appointments() will update the alarm timer when this
+ async call is done, so no need to restart the timer here... */
+ update_appointment_lists (self);
+
+ return G_SOURCE_REMOVE;
+}
+
+/* if there are upcoming alarms, set the alarm timer to the nearest one.
+ otherwise, unset the alarm timer. */
+static void
+set_alarm_timer (IndicatorDatetimeService * self)
+{
+ priv_t * p;
+ GDateTime * now;
+ GDateTime * alarm_time;
+ GSList * l;
+
+ p = self->priv;
+ indicator_clear_timer (&p->alarm_timer);
+
+ now = indicator_datetime_service_get_localtime (self);
+
+ /* find the time of the next alarm on our calendar */
+ alarm_time = NULL;
+ for (l=p->upcoming_appointments; l!=NULL; l=l->next)
+ {
+ const struct IndicatorDatetimeAppt * appt = l->data;
+
+ if (appointment_has_alarm_url (appt))
+ if (g_date_time_compare (appt->begin, now) > 0)
+ if (!alarm_time || g_date_time_compare (alarm_time, appt->begin) > 0)
+ alarm_time = appt->begin;
+ }
+
+ /* if there's an upcoming alarm, set a timer to wake up at that time */
+ if (alarm_time != NULL)
+ {
+ GTimeSpan interval_msec;
+ gchar * str;
+ GDateTime * then;
+
+ interval_msec = g_date_time_difference (alarm_time, now);
+ interval_msec += G_USEC_PER_SEC; /* fire a moment after alarm_time */
+ interval_msec /= 1000; /* convert from usec to msec */
+
+ str = g_date_time_format (alarm_time, "%F %T");
+ g_debug ("%s is the next alarm time", str);
+ g_free (str);
+ then = g_date_time_add_seconds (now, interval_msec/1000);
+ str = g_date_time_format (then, "%F %T");
+ g_debug ("%s is when we'll wake up for it", str);
+ g_free (str);
+ g_date_time_unref (then);
+
+ p->alarm_timer = g_timeout_add_full (G_PRIORITY_HIGH,
+ (guint) interval_msec,
+ on_alarm_timer,
+ self,
+ NULL);
+ }
+
+ g_date_time_unref (now);
+}
+
+/***
+****
+***/
+
static void
update_internal_timezone (IndicatorDatetimeService * self)
{
@@ -743,23 +944,36 @@ get_appointment_time_format (struct IndicatorDatetimeAppt * appt,
}
static void
-add_appointments (IndicatorDatetimeService * self, GMenu * menu, gboolean terse)
+add_appointments (IndicatorDatetimeService * self, GMenu * menu, gboolean phone)
{
const int MAX_APPTS = 5;
- GDateTime * now = indicator_datetime_service_get_localtime (self);
+ GDateTime * now;
+ GHashTable * added;
GSList * appts;
GSList * l;
int i;
+ now = indicator_datetime_service_get_localtime (self);
+
+ added = g_hash_table_new (g_str_hash, g_str_equal);
+
/* build appointment menuitems */
appts = self->priv->upcoming_appointments;
for (l=appts, i=0; l!=NULL && i<MAX_APPTS; l=l->next, i++)
{
struct IndicatorDatetimeAppt * appt = l->data;
- char * fmt = get_appointment_time_format (appt, now, self->priv->settings, terse);
- const gint64 unix_time = g_date_time_to_unix (appt->begin);
+ char * fmt;
+ gint64 unix_time;
GMenuItem * menu_item;
+ if (g_hash_table_contains (added, appt->uid))
+ continue;
+
+ g_hash_table_add (added, appt->uid);
+
+ fmt = get_appointment_time_format (appt, now, self->priv->settings, phone);
+ unix_time = g_date_time_to_unix (appt->begin);
+
menu_item = g_menu_item_new (appt->summary, NULL);
if (appt->has_alarms)
@@ -776,15 +990,22 @@ add_appointments (IndicatorDatetimeService * self, GMenu * menu, gboolean terse)
g_menu_item_set_attribute (menu_item, "x-canonical-type",
"s", appt->has_alarms ? "com.canonical.indicator.alarm"
: "com.canonical.indicator.appointment");
- g_menu_item_set_action_and_target_value (menu_item,
- "indicator.activate-planner",
- g_variant_new_int64 (unix_time));
+
+ if (phone)
+ g_menu_item_set_action_and_target_value (menu_item,
+ "indicator.activate-appointment",
+ g_variant_new_string (appt->uid));
+ else
+ g_menu_item_set_action_and_target_value (menu_item,
+ "indicator.activate-planner",
+ g_variant_new_int64 (unix_time));
g_menu_append_item (menu, menu_item);
g_object_unref (menu_item);
g_free (fmt);
}
/* cleanup */
+ g_hash_table_unref (added);
g_date_time_unref (now);
}
@@ -1363,6 +1584,34 @@ on_phone_settings_activated (GSimpleAction * a G_GNUC_UNUSED,
}
static void
+on_activate_appointment (GSimpleAction * a G_GNUC_UNUSED,
+ GVariant * param,
+ gpointer gself)
+{
+ priv_t * p = INDICATOR_DATETIME_SERVICE(gself)->priv;
+ const gchar * uid = g_variant_get_string (param, NULL);
+
+ if (uid != NULL)
+ {
+ const struct IndicatorDatetimeAppt * appt;
+ GSList * l;
+
+ /* find the appointment that matches that uid */
+ for (l=p->upcoming_appointments, appt=NULL; l && !appt; l=l->next)
+ {
+ const struct IndicatorDatetimeAppt * tmp = l->data;
+ if (!g_strcmp0 (uid, tmp->uid))
+ appt = tmp;
+ }
+
+ /* if that appointment's an alarm, dispatch its url */
+ g_debug ("%s: uri '%s'; matching appt is %p", G_STRFUNC, uid, appt);
+ if (appt && appointment_has_alarm_url (appt))
+ dispatch_alarm_url (appt);
+ }
+}
+
+static void
on_phone_clock_activated (GSimpleAction * a G_GNUC_UNUSED,
GVariant * param G_GNUC_UNUSED,
gpointer gself G_GNUC_UNUSED)
@@ -1426,6 +1675,7 @@ init_gactions (IndicatorDatetimeService * self)
{ "activate-phone-settings", on_phone_settings_activated },
{ "activate-phone-clock-app", on_phone_clock_activated },
{ "activate-planner", on_activate_planner, "x", NULL },
+ { "activate-appointment", on_activate_appointment, "s", NULL },
{ "set-location", on_set_location, "s" }
};
@@ -1656,6 +1906,10 @@ set_upcoming_appointments (IndicatorDatetimeService * self,
/* sync the menus/actions */
rebuild_appointments_section_soon (self);
+
+ /* alarm timer is keyed off of the next alarm time,
+ so it needs to be rebuilt when the appointment list changes */
+ set_alarm_timer (self);
}
static void
@@ -1825,6 +2079,45 @@ on_name_lost (GDBusConnection * connection G_GNUC_UNUSED,
***/
static void
+my_get_property (GObject * o,
+ guint property_id,
+ GValue * value,
+ GParamSpec * pspec)
+{
+ IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (o);
+
+ switch (property_id)
+ {
+ case PROP_PLANNER:
+ g_value_set_object (value, self->priv->planner);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec);
+ }
+}
+
+static void
+my_set_property (GObject * o,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (o);
+
+ switch (property_id)
+ {
+ case PROP_PLANNER:
+ indicator_datetime_service_set_planner (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec);
+ }
+}
+
+
+static void
my_dispose (GObject * o)
{
int i;
@@ -1847,13 +2140,7 @@ my_dispose (GObject * o)
set_detect_location_enabled (self, FALSE);
- if (p->planner != NULL)
- {
- g_signal_handlers_disconnect_by_data (p->planner, self);
- g_clear_object (&p->planner);
- }
- g_clear_pointer (&p->upcoming_appointments, indicator_datetime_planner_free_appointments);
- g_clear_pointer (&p->calendar_appointments, indicator_datetime_planner_free_appointments);
+ indicator_datetime_service_set_planner (self, NULL);
if (p->login1_manager != NULL)
{
@@ -1865,6 +2152,7 @@ my_dispose (GObject * o)
indicator_clear_timer (&p->rebuild_id);
indicator_clear_timer (&p->timezone_timer);
indicator_clear_timer (&p->header_timer);
+ indicator_clear_timer (&p->alarm_timer);
if (p->settings != NULL)
{
@@ -1955,16 +2243,6 @@ indicator_datetime_service_init (IndicatorDatetimeService * self)
p->cancellable = g_cancellable_new ();
/***
- **** Create the planner and listen for changes
- ***/
-
- p->planner = indicator_datetime_planner_eds_new ();
-
- g_signal_connect_swapped (p->planner, "appointments-changed",
- G_CALLBACK(update_appointment_lists), self);
-
-
- /***
**** Create the settings object and listen for changes
***/
@@ -2033,6 +2311,8 @@ indicator_datetime_service_init (IndicatorDatetimeService * self)
on_local_time_jumped (self);
+ set_alarm_timer (self);
+
for (i=0; i<N_PROFILES; ++i)
create_menu (self, i);
@@ -2043,9 +2323,12 @@ static void
indicator_datetime_service_class_init (IndicatorDatetimeServiceClass * klass)
{
GObjectClass * object_class = G_OBJECT_CLASS (klass);
+ const GParamFlags flags = G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS;
object_class->dispose = my_dispose;
object_class->finalize = my_finalize;
+ object_class->get_property = my_get_property;
+ object_class->set_property = my_set_property;
g_type_class_add_private (klass, sizeof (IndicatorDatetimeServicePrivate));
@@ -2057,6 +2340,18 @@ indicator_datetime_service_class_init (IndicatorDatetimeServiceClass * klass)
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
+
+ /* install properties */
+
+ properties[PROP_0] = NULL;
+
+ properties[PROP_PLANNER] = g_param_spec_object ("planner",
+ "Planner",
+ "The appointment provider",
+ INDICATOR_TYPE_DATETIME_PLANNER,
+ flags);
+
+ g_object_class_install_properties (object_class, PROP_LAST, properties);
}
/***
@@ -2064,9 +2359,11 @@ indicator_datetime_service_class_init (IndicatorDatetimeServiceClass * klass)
***/
IndicatorDatetimeService *
-indicator_datetime_service_new (void)
+indicator_datetime_service_new (IndicatorDatetimePlanner * planner)
{
- GObject * o = g_object_new (INDICATOR_TYPE_DATETIME_SERVICE, NULL);
+ GObject * o = g_object_new (INDICATOR_TYPE_DATETIME_SERVICE,
+ "planner", planner,
+ NULL);
return INDICATOR_DATETIME_SERVICE (o);
}
@@ -2102,3 +2399,38 @@ indicator_datetime_service_set_calendar_date (IndicatorDatetimeService * self,
if (dirty)
update_appointment_lists (self);
}
+
+void
+indicator_datetime_service_set_planner (IndicatorDatetimeService * self,
+ IndicatorDatetimePlanner * planner)
+{
+ priv_t * p;
+
+ g_return_if_fail (INDICATOR_IS_DATETIME_SERVICE (self));
+ g_return_if_fail (INDICATOR_IS_DATETIME_PLANNER (planner));
+
+ p = self->priv;
+
+ /* clear the old planner & appointments */
+
+ if (p->planner != NULL)
+ {
+ g_signal_handlers_disconnect_by_data (p->planner, self);
+ g_clear_object (&p->planner);
+ }
+
+ g_clear_pointer (&p->upcoming_appointments, indicator_datetime_planner_free_appointments);
+ g_clear_pointer (&p->calendar_appointments, indicator_datetime_planner_free_appointments);
+
+ /* set the new planner & begin fetching appointments from it */
+
+ if (planner != NULL)
+ {
+ p->planner = g_object_ref (planner);
+
+ g_signal_connect_swapped (p->planner, "appointments-changed",
+ G_CALLBACK(update_appointment_lists), self);
+
+ update_appointment_lists (self);
+ }
+}