Back to index

indicator-datetime  12.10.0
datetime-service.c
Go to the documentation of this file.
00001 /*-*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
00002 /*
00003 An indicator to time and date related information in the menubar.
00004 
00005 Copyright 2010 Canonical Ltd.
00006 
00007 Authors:
00008     Ted Gould <ted@canonical.com>
00009 
00010 This program is free software: you can redistribute it and/or modify it 
00011 under the terms of the GNU General Public License version 3, as published 
00012 by the Free Software Foundation.
00013 
00014 This program is distributed in the hope that it will be useful, but 
00015 WITHOUT ANY WARRANTY; without even the implied warranties of 
00016 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR 
00017 PURPOSE.  See the GNU General Public License for more details.
00018 
00019 You should have received a copy of the GNU General Public License along 
00020 with this program.  If not, see <http://www.gnu.org/licenses/>.
00021 */
00022 
00023 #include <config.h>
00024 #include <libindicator/indicator-service.h>
00025 #include <locale.h>
00026 
00027 #include <gtk/gtk.h>
00028 #include <gdk/gdk.h>
00029 #include <glib/gi18n.h>
00030 #include <gio/gio.h>
00031 #include <math.h>
00032 #include <gconf/gconf-client.h>
00033 
00034 #include <libdbusmenu-gtk/menuitem.h>
00035 #include <libdbusmenu-glib/server.h>
00036 #include <libdbusmenu-glib/client.h>
00037 #include <libdbusmenu-glib/menuitem.h>
00038 
00039 #include <geoclue/geoclue-master.h>
00040 #include <geoclue/geoclue-master-client.h>
00041 
00042 #include <time.h>
00043 #include <libecal/libecal.h>
00044 #include <libical/ical.h>
00045 #include <libedataserver/libedataserver.h>
00046 // Other users of ecal seem to also include these, not sure why they should be included by the above
00047 #include <libical/icaltime.h>
00048 #include <cairo/cairo.h>
00049 
00050 #include "datetime-interface.h"
00051 #include "dbus-shared.h"
00052 #include "settings-shared.h"
00053 #include "utils.h"
00054 
00055 #ifdef HAVE_CCPANEL
00056  #define SETTINGS_APP_INVOCATION "gnome-control-center indicator-datetime"
00057 #else
00058  #define SETTINGS_APP_INVOCATION "gnome-control-center datetime"
00059 #endif
00060 
00061 static void geo_create_client (GeoclueMaster * master, GeoclueMasterClient * client, gchar * path, GError * error, gpointer user_data);
00062 static gboolean update_appointment_menu_items (gpointer user_data);
00063 static void update_location_menu_items (void);
00064 static void setup_timer (void);
00065 static void geo_client_invalid (GeoclueMasterClient * client, gpointer user_data);
00066 static void geo_address_change (GeoclueMasterClient * client, gchar * a, gchar * b, gchar * c, gchar * d, gpointer user_data);
00067 static gboolean get_greeter_mode (void);
00068 
00069 static void quick_set_tz (DbusmenuMenuitem * menuitem, guint timestamp, gpointer user_data);
00070 
00071 static IndicatorService * service = NULL;
00072 static GMainLoop * mainloop = NULL;
00073 static DbusmenuServer * server = NULL;
00074 static DbusmenuMenuitem * root = NULL;
00075 static DatetimeInterface * dbus = NULL;
00076 
00077 /* Global Items */
00078 static DbusmenuMenuitem * date = NULL;
00079 static DbusmenuMenuitem * calendar = NULL;
00080 static DbusmenuMenuitem * settings = NULL;
00081 static DbusmenuMenuitem * events_separator = NULL;
00082 static DbusmenuMenuitem * locations_separator = NULL;
00083 static DbusmenuMenuitem * add_appointment = NULL;
00084 static GList            * appointments = NULL;
00085 static GSList           * location_menu_items = NULL;
00086 static GList            * comp_instances = NULL;
00087 static gboolean           updating_appointments = FALSE;
00088 static time_t             start_time_appointments = (time_t) 0;
00089 static GSettings        * conf = NULL;
00090 static GConfClient      * gconf = NULL;
00091 
00092 
00093 /* Geoclue trackers */
00094 static GeoclueMasterClient * geo_master = NULL;
00095 static GeoclueAddress * geo_address = NULL;
00096 
00097 /* Our 2 important timezones */
00098 static gchar                * current_timezone = NULL;
00099 static gchar                * geo_timezone = NULL;
00100 
00101 struct comp_instance {
00102         ECalComponent *comp;
00103         time_t start;
00104         time_t end;
00105         ESource *source;
00106 };
00107 
00111 struct TimeLocation
00112 {
00113        gint32 offset;
00114        gchar * zone;
00115        gchar * name;
00116        gboolean visible;
00117 };
00118 static void
00119 time_location_free (struct TimeLocation * loc)
00120 {
00121        g_free (loc->name);
00122        g_free (loc->zone);
00123        g_free (loc);
00124 }
00125 static struct TimeLocation*
00126 time_location_new (const char * zone, const char * name, gboolean visible, time_t now)
00127 {
00128        struct TimeLocation * loc = g_new (struct TimeLocation, 1);
00129        GTimeZone * tz = g_time_zone_new (zone);
00130        gint interval = g_time_zone_find_interval (tz, G_TIME_TYPE_UNIVERSAL, now);
00131        loc->offset = g_time_zone_get_offset (tz, interval);
00132        loc->zone = g_strdup (zone);
00133        loc->name = g_strdup (name);
00134        loc->visible = visible;
00135        g_time_zone_unref (tz);
00136        g_debug ("%s zone '%s' name '%s' offset is %d", G_STRLOC, zone, name, (int)loc->offset);
00137        return loc;
00138 }
00139 static int
00140 time_location_compare (const struct TimeLocation * a, const struct TimeLocation * b)
00141 {
00142        int ret = a->offset - b->offset; /* primary key */
00143        if (!ret)
00144               ret = g_strcmp0 (a->name, b->name); /* secondary key */
00145        if (!ret)
00146               ret = a->visible - b->visible; /* tertiary key */
00147        g_debug ("%s comparing '%s' (%d) to '%s' (%d), returning %d", G_STRLOC, a->name, (int)a->offset, b->name, (int)b->offset, ret);
00148        return ret;
00149 }
00150 static GSList*
00151 locations_add (GSList * locations, const char * zone, const char * name, gboolean visible, time_t now)
00152 {
00153        struct TimeLocation * loc = time_location_new (zone, name, visible, now);
00154 
00155        if (g_slist_find_custom (locations, loc, (GCompareFunc)time_location_compare) == NULL) {
00156               g_debug ("%s Adding zone '%s', name '%s'", G_STRLOC, zone, name);
00157               locations = g_slist_append (locations, loc);
00158        } else {
00159               g_debug("%s Skipping duplicate zone '%s' name '%s'", G_STRLOC, zone, name);
00160               time_location_free (loc);
00161        }
00162        return locations;
00163 }
00164 
00165 /* Update the timezone entries */
00166 static void
00167 update_location_menu_items (void)
00168 {
00169        /* if we're in greeter mode, don't bother */
00170        if (locations_separator == NULL)
00171               return;
00172 
00173        /* remove the previous locations */
00174        while (location_menu_items != NULL) {
00175               DbusmenuMenuitem * item = DBUSMENU_MENUITEM(location_menu_items->data);
00176               location_menu_items = g_slist_remove(location_menu_items, item);
00177               dbusmenu_menuitem_child_delete(root, DBUSMENU_MENUITEM(item));
00178               g_object_unref(G_OBJECT(item));
00179        }
00180 
00181        /***
00182        ****  Build a list of locations to add: use geo_timezone,
00183        ****  current_timezone, and SETTINGS_LOCATIONS_S, but omit duplicates.
00184        ***/
00185 
00186        GSList * locations = NULL;
00187        const time_t now = time(NULL);
00188 
00189        /* maybe add geo_timezone */
00190        if (geo_timezone != NULL) {
00191               const gboolean visible = g_settings_get_boolean (conf, SETTINGS_SHOW_DETECTED_S);
00192               gchar * name = get_current_zone_name (geo_timezone);
00193               locations = locations_add (locations, geo_timezone, name, visible, now);
00194               g_free (name);
00195        }
00196 
00197        /* maybe add current_timezone */
00198        if (current_timezone != NULL) {
00199               const gboolean visible = g_settings_get_boolean (conf, SETTINGS_SHOW_DETECTED_S);
00200               gchar * name = get_current_zone_name (current_timezone);
00201               locations = locations_add (locations, current_timezone, name, visible, now);
00202               g_free (name);
00203        }
00204 
00205        /* maybe add the user-specified custom locations */
00206        gchar ** user_locations = g_settings_get_strv (conf, SETTINGS_LOCATIONS_S);
00207        if (user_locations != NULL) { 
00208               gint i;
00209               const guint location_count = g_strv_length (user_locations);
00210               const gboolean visible = g_settings_get_boolean (conf, SETTINGS_SHOW_LOCATIONS_S);
00211               g_debug ("%s Found %u user-specified locations", G_STRLOC, location_count);
00212               for (i=0; i<location_count; i++) {
00213                      gchar * zone;
00214                      gchar * name;
00215                      split_settings_location (user_locations[i], &zone, &name);
00216                      locations = locations_add (locations, zone, name, visible, now);
00217                      g_free (name);
00218                      g_free (zone);
00219               }
00220               g_strfreev (user_locations);
00221               user_locations = NULL;
00222        }
00223 
00224        /* finally create menuitems for each location */
00225        gint offset = dbusmenu_menuitem_get_position (locations_separator, root)+1;
00226        GSList * l;
00227        gboolean have_visible_location = FALSE;
00228        for (l=locations; l!=NULL; l=l->next) {
00229               struct TimeLocation * loc = l->data;
00230               g_debug("%s Adding location: zone '%s', name '%s'", G_STRLOC, loc->zone, loc->name);
00231               DbusmenuMenuitem * item = dbusmenu_menuitem_new();
00232               dbusmenu_menuitem_property_set      (item, DBUSMENU_MENUITEM_PROP_TYPE, TIMEZONE_MENUITEM_TYPE);
00233               dbusmenu_menuitem_property_set      (item, TIMEZONE_MENUITEM_PROP_NAME, loc->name);
00234               dbusmenu_menuitem_property_set      (item, TIMEZONE_MENUITEM_PROP_ZONE, loc->zone);
00235               dbusmenu_menuitem_property_set_bool (item, TIMEZONE_MENUITEM_PROP_RADIO, FALSE);
00236               dbusmenu_menuitem_property_set_bool (item, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE);
00237               dbusmenu_menuitem_property_set_bool (item, DBUSMENU_MENUITEM_PROP_VISIBLE, loc->visible);
00238               dbusmenu_menuitem_child_add_position (root, item, offset++);
00239               g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, G_CALLBACK(quick_set_tz), NULL);
00240               location_menu_items = g_slist_append (location_menu_items, item);
00241               if (loc->visible)
00242                      have_visible_location = TRUE;
00243               time_location_free (loc);
00244        }
00245        g_slist_free (locations);
00246        locations = NULL;
00247 
00248        /* if there's at least one item being shown, show the separator too */
00249        dbusmenu_menuitem_property_set_bool (locations_separator, DBUSMENU_MENUITEM_PROP_VISIBLE, have_visible_location);
00250 }
00251 
00252 /* Update the current timezone */
00253 static void
00254 update_current_timezone (void) {
00255        /* Clear old data */
00256        if (current_timezone != NULL) {
00257               g_free(current_timezone);
00258               current_timezone = NULL;
00259        }
00260 
00261        current_timezone = read_timezone ();
00262        if (current_timezone == NULL) {
00263               return;
00264        }
00265 
00266        g_debug("System timezone is: %s", current_timezone);
00267 
00268        update_location_menu_items();
00269 
00270        return;
00271 }
00272 
00273 static void
00274 quick_set_tz_cb (GObject *object, GAsyncResult *res, gpointer data)
00275 {
00276   GError * error = NULL;
00277   GVariant * answers = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), res, &error);
00278 
00279   if (error != NULL) {
00280     g_warning("Could not set timezone for SettingsDaemon: %s", error->message);
00281     g_clear_error (&error);
00282     return;
00283   }
00284 
00285   g_variant_unref (answers);
00286 }
00287 
00288 static void
00289 quick_set_tz_proxy_cb (GObject *object, GAsyncResult *res, gpointer zone)
00290 {
00291        GError * error = NULL;
00292 
00293        GDBusProxy * proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
00294 
00295        if (error != NULL) {
00296               g_warning("Could not grab DBus proxy for SettingsDaemon: %s", error->message);
00297               g_clear_error (&error);
00298               g_free (zone);
00299               return;
00300        }
00301 
00302        g_dbus_proxy_call (proxy, "SetTimezone", g_variant_new ("(s)", zone),
00303                           G_DBUS_CALL_FLAGS_NONE, -1, NULL, quick_set_tz_cb, NULL);
00304        g_free (zone);
00305        g_object_unref (proxy);
00306 }
00307 
00308 static void
00309 quick_set_tz (DbusmenuMenuitem * menuitem, guint timestamp, gpointer user_data)
00310 {
00311        const gchar * tz = dbusmenu_menuitem_property_get(menuitem, TIMEZONE_MENUITEM_PROP_ZONE);
00312        g_debug("Quick setting timezone to: %s", tz);
00313 
00314        g_return_if_fail(tz != NULL);
00315 
00316        const gchar * name = dbusmenu_menuitem_property_get(menuitem, TIMEZONE_MENUITEM_PROP_NAME);
00317 
00318        /* Set it in gsettings so we don't lose user's preferred name */
00319        GSettings * conf = g_settings_new (SETTINGS_INTERFACE);
00320        gchar * tz_full = g_strdup_printf ("%s %s", tz, name);
00321        g_settings_set_string (conf, SETTINGS_TIMEZONE_NAME_S, tz_full);
00322        g_free (tz_full);
00323        g_object_unref (conf);
00324 
00325        g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, NULL,
00326                                  "org.gnome.SettingsDaemon.DateTimeMechanism",
00327                                  "/",                            
00328                                  "org.gnome.SettingsDaemon.DateTimeMechanism",
00329                                  NULL, quick_set_tz_proxy_cb, g_strdup (tz));
00330 
00331        return;
00332 }
00333 
00334 /* Updates the label in the date menuitem */
00335 static gboolean
00336 update_datetime (gpointer user_data)
00337 {
00338        GDateTime *datetime;
00339        gchar *utf8;
00340 
00341        g_debug("Updating Date/Time");
00342 
00343        datetime = g_date_time_new_now_local ();
00344        if (datetime == NULL) {
00345               g_warning("Error getting local time");
00346               dbusmenu_menuitem_property_set(date, DBUSMENU_MENUITEM_PROP_LABEL, _("Error getting time"));
00347               g_date_time_unref (datetime);
00348               return FALSE;
00349        }
00350 
00351        /* eranslators: strftime(3) style date format on top of the menu when you click on the clock */
00352         utf8 = g_date_time_format (datetime, _("%A, %e %B %Y"));
00353 
00354        dbusmenu_menuitem_property_set(date, DBUSMENU_MENUITEM_PROP_LABEL, utf8);
00355 
00356        g_date_time_unref (datetime);
00357        g_free(utf8);
00358 
00359        return FALSE;
00360 }
00361 
00362 /* Run a particular program based on an activation */
00363 static void
00364 execute_command (const gchar * command)
00365 {
00366        GError * error = NULL;
00367 
00368        g_debug("Issuing command '%s'", command);
00369        if (!g_spawn_command_line_async(command, &error)) {
00370               g_warning("Unable to start %s: %s", (char *)command, error->message);
00371               g_clear_error (&error);
00372        }
00373 }
00374 
00375 /* Run a particular program based on an activation */
00376 static void
00377 activate_cb (DbusmenuMenuitem  * menuitem  G_GNUC_UNUSED,
00378              guint               timestamp G_GNUC_UNUSED,
00379              const gchar       * command)
00380 {
00381        execute_command (command);
00382 }
00383 
00384 static gboolean
00385 update_appointment_menu_items_idle (gpointer user_data)
00386 {
00387        update_appointment_menu_items(user_data);
00388        return FALSE;
00389 }
00390 
00391 static gboolean
00392 month_changed_cb (DbusmenuMenuitem * menuitem, gchar *name, GVariant *variant, guint timestamp)
00393 {
00394        start_time_appointments = (time_t)g_variant_get_uint32(variant);
00395        
00396        g_debug("Received month changed with timestamp: %d -> %s",(int)start_time_appointments, ctime(&start_time_appointments));     
00397        /* By default one of the first things we do is
00398           clear the marks as we don't know the correct
00399           ones yet and we don't want to confuse the
00400           user. */
00401        dbusmenu_menuitem_property_remove(menuitem, CALENDAR_MENUITEM_PROP_MARKS);
00402 
00403        GList * appointment;
00404        for (appointment = appointments; appointment != NULL; appointment = g_list_next(appointment)) {
00405               DbusmenuMenuitem * mi = DBUSMENU_MENUITEM(appointment->data);
00406               dbusmenu_menuitem_property_set_bool(mi, DBUSMENU_MENUITEM_PROP_ENABLED, FALSE);
00407        }
00408 
00409        g_idle_add(update_appointment_menu_items_idle, NULL);
00410        return TRUE;
00411 }
00412 
00413 static gboolean
00414 day_selected_cb (DbusmenuMenuitem * menuitem, gchar *name, GVariant *variant, guint timestamp)
00415 {
00416        time_t new_time = (time_t)g_variant_get_uint32(variant);
00417        g_warn_if_fail(new_time != 0);
00418 
00419        if (start_time_appointments == 0 || new_time == 0) {
00420               /* If we've got nothing, assume everyhting is going to
00421                  get repopulated, let's start with a clean slate */
00422               dbusmenu_menuitem_property_remove(menuitem, CALENDAR_MENUITEM_PROP_MARKS);
00423        } else {
00424               /* No check to see if we changed months.  If we did we'll
00425                  want to clear the marks.  Otherwise we're cool keeping
00426                  them around. */
00427               struct tm start_tm;
00428               struct tm new_tm;
00429 
00430               localtime_r(&start_time_appointments, &start_tm);
00431               localtime_r(&new_time, &new_tm);
00432 
00433               if (start_tm.tm_mon != new_tm.tm_mon) {
00434                      dbusmenu_menuitem_property_remove(menuitem, CALENDAR_MENUITEM_PROP_MARKS);
00435               }
00436        }
00437 
00438        GList * appointment;
00439        for (appointment = appointments; appointment != NULL; appointment = g_list_next(appointment)) {
00440               DbusmenuMenuitem * mi = DBUSMENU_MENUITEM(appointment->data);
00441               dbusmenu_menuitem_property_set_bool(mi, DBUSMENU_MENUITEM_PROP_ENABLED, FALSE);
00442        }
00443 
00444        start_time_appointments = new_time;
00445 
00446        g_debug("Received day-selected with timestamp: %d -> %s",(int)start_time_appointments, ctime(&start_time_appointments));      
00447        g_idle_add(update_appointment_menu_items_idle, NULL);
00448 
00449        return TRUE;
00450 }
00451 
00452 static gboolean
00453 day_selected_double_click_cb (DbusmenuMenuitem * menuitem  G_GNUC_UNUSED,
00454                               gchar            * name      G_GNUC_UNUSED,
00455                               GVariant         * variant,
00456                               guint              timestamp G_GNUC_UNUSED)
00457 {
00458        const time_t evotime = (time_t)g_variant_get_uint32(variant);
00459        
00460        g_debug("Received day-selected-double-click with timestamp: %d -> %s",(int)evotime, ctime(&evotime));    
00461        
00462        gchar *ad = isodate_from_time_t(evotime);
00463        gchar *cmd = g_strconcat("evolution calendar:///?startdate=", ad, NULL);
00464        
00465        execute_command (cmd);
00466 
00467        g_free (cmd);
00468        g_free (ad);
00469        
00470        return TRUE;
00471 }
00472 
00473 static guint ecaltimer = 0;
00474 
00475 static void
00476 start_ecal_timer(void)
00477 {
00478        if (ecaltimer != 0) {
00479               g_source_remove(ecaltimer);
00480               ecaltimer = 0;
00481        }
00482        if (update_appointment_menu_items(NULL))
00483               ecaltimer = g_timeout_add_seconds(60*5, update_appointment_menu_items, NULL);       
00484 }
00485 
00486 static void
00487 stop_ecal_timer(void)
00488 {
00489        if (ecaltimer != 0) {
00490               g_source_remove(ecaltimer);
00491               ecaltimer = 0;
00492        }
00493 }
00494 static gboolean
00495 idle_start_ecal_timer (gpointer data)
00496 {
00497        start_ecal_timer();
00498        return FALSE;
00499 }
00500 
00501 static void
00502 show_events_changed (void)
00503 {
00504        if (g_settings_get_boolean(conf, SETTINGS_SHOW_EVENTS_S)) {
00505               dbusmenu_menuitem_property_set_bool(add_appointment, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE);
00506               dbusmenu_menuitem_property_set_bool(events_separator, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE);
00507               start_ecal_timer();
00508        } else {
00509               dbusmenu_menuitem_property_set_bool(add_appointment, DBUSMENU_MENUITEM_PROP_VISIBLE, FALSE);
00510               dbusmenu_menuitem_property_set_bool(events_separator, DBUSMENU_MENUITEM_PROP_VISIBLE, FALSE);
00511               /* Remove all of the previous appointments */
00512               if (appointments != NULL) {
00513                      g_debug("Hiding old appointments");
00514                      GList * appointment;
00515                      for (appointment = appointments; appointment != NULL; appointment = g_list_next(appointment)) {
00516                             DbusmenuMenuitem * litem =  DBUSMENU_MENUITEM(appointment->data);
00517                             g_debug("Hiding old appointment: %p", litem);
00518                             // Remove all the existing menu items which are in appointments.
00519                             dbusmenu_menuitem_property_set_bool(DBUSMENU_MENUITEM(litem), DBUSMENU_MENUITEM_PROP_VISIBLE, FALSE);
00520                      }
00521               }
00522               stop_ecal_timer();
00523        }
00524 }
00525 
00526 static gboolean
00527 calendar_app_is_usable (void)
00528 {
00529        /* confirm that it's installed... */
00530        gchar *evo = g_find_program_in_path("evolution");
00531        if (evo == NULL)
00532               return FALSE;
00533        g_debug ("found calendar app: '%s'", evo);
00534        g_free (evo);
00535 
00536        /* confirm that it's got an account set up... */
00537        GSList *accounts_list = gconf_client_get_list (gconf, "/apps/evolution/mail/accounts", GCONF_VALUE_STRING, NULL);
00538        const guint n = g_slist_length (accounts_list);
00539        g_debug ("found %u evolution accounts", n);
00540        g_slist_free_full (accounts_list, g_free);
00541        return n > 0;
00542 }
00543 
00544 /* Looks for the calendar application and enables the item if
00545    we have one, starts ecal timer if events are turned on */
00546 static gboolean
00547 check_for_calendar (gpointer user_data)
00548 {
00549        g_return_val_if_fail (calendar != NULL, FALSE);
00550        
00551        dbusmenu_menuitem_property_set_bool(date, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE);
00552        
00553        if (!get_greeter_mode () && calendar_app_is_usable()) {
00554               
00555               g_signal_connect (G_OBJECT(date), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00556                                 G_CALLBACK (activate_cb), "evolution -c calendar");
00557               
00558               events_separator = dbusmenu_menuitem_new();
00559               dbusmenu_menuitem_property_set(events_separator, DBUSMENU_MENUITEM_PROP_TYPE, DBUSMENU_CLIENT_TYPES_SEPARATOR);
00560               dbusmenu_menuitem_child_add_position(root, events_separator, 2);
00561               add_appointment = dbusmenu_menuitem_new();
00562               dbusmenu_menuitem_property_set (add_appointment, DBUSMENU_MENUITEM_PROP_LABEL, _("Add Event…"));
00563               dbusmenu_menuitem_property_set_bool(add_appointment, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE);
00564               g_signal_connect(G_OBJECT(add_appointment), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, G_CALLBACK(activate_cb), "evolution -c calendar");
00565               dbusmenu_menuitem_child_add_position (root, add_appointment, 3);
00566 
00567 
00568               if (g_settings_get_boolean(conf, SETTINGS_SHOW_EVENTS_S)) {
00569                      dbusmenu_menuitem_property_set_bool(add_appointment, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE);
00570                      dbusmenu_menuitem_property_set_bool(events_separator, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE);
00571                      g_idle_add((GSourceFunc)idle_start_ecal_timer, NULL);
00572               } else {
00573                      dbusmenu_menuitem_property_set_bool(add_appointment, DBUSMENU_MENUITEM_PROP_VISIBLE, FALSE);
00574                      dbusmenu_menuitem_property_set_bool(events_separator, DBUSMENU_MENUITEM_PROP_VISIBLE, FALSE);
00575                      stop_ecal_timer();
00576               }
00577               
00578               // Connect to calendar events
00579               g_signal_connect(calendar, "event::month-changed", G_CALLBACK(month_changed_cb), NULL);
00580               g_signal_connect(calendar, "event::day-selected", G_CALLBACK(day_selected_cb), NULL);
00581               g_signal_connect(calendar, "event::day-selected-double-click", G_CALLBACK(day_selected_double_click_cb), NULL);
00582        } else {
00583               g_debug("Unable to find calendar app.");
00584               if (add_appointment != NULL)
00585                      dbusmenu_menuitem_property_set_bool(add_appointment, DBUSMENU_MENUITEM_PROP_VISIBLE, FALSE);
00586               if (events_separator != NULL)
00587                      dbusmenu_menuitem_property_set_bool(events_separator, DBUSMENU_MENUITEM_PROP_VISIBLE, FALSE);
00588        }
00589        
00590        if (g_settings_get_boolean(conf, SETTINGS_SHOW_CALENDAR_S)) {
00591               dbusmenu_menuitem_property_set_bool(calendar, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE);
00592               dbusmenu_menuitem_property_set_bool(calendar, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE);
00593        } else {
00594               dbusmenu_menuitem_property_set_bool(calendar, DBUSMENU_MENUITEM_PROP_ENABLED, FALSE);
00595               dbusmenu_menuitem_property_set_bool(calendar, DBUSMENU_MENUITEM_PROP_VISIBLE, FALSE);
00596        }
00597 
00598        return FALSE;
00599 }
00600 
00601 static gint
00602 compare_comp_instances (gconstpointer ga, gconstpointer gb)
00603 {
00604        const struct comp_instance * a = ga;
00605        const struct comp_instance * b = gb;
00606 
00607        /* sort by start time */
00608        if (a->start < b->start) return -1;
00609        if (a->start > b->start) return  1;
00610        return 0;
00611 }
00612 
00613 static struct comp_instance*
00614 comp_instance_new (ECalComponent * comp, time_t start, time_t end, ESource * source)
00615 {
00616        g_debug("Using times start %s, end %s", ctime(&start), ctime(&end));
00617 
00618        struct comp_instance *ci = g_new (struct comp_instance, 1);
00619        ci->comp = g_object_ref (comp);
00620        ci->source = source;
00621        ci->start = start;
00622        ci->end = end;
00623        return ci;
00624 }
00625 static void
00626 comp_instance_free (struct comp_instance* ci)
00627 {
00628        if (ci != NULL) {
00629               g_clear_object (&ci->comp);
00630               g_free (ci);
00631        }
00632 }
00633 
00634 static gboolean
00635 populate_appointment_instances (ECalClient * client,
00636                                 time_t          start,
00637                                 time_t          end,
00638                                 gpointer        data)
00639 {
00640        GSList *ecalcomps, *comp_item;
00641 
00642        if (e_cal_client_get_object_list_as_comps_sync (client,
00643                                                        NULL,
00644                                                        &ecalcomps,
00645                                                        NULL, NULL)) {
00646 
00647                for (comp_item = ecalcomps; comp_item; comp_item = g_slist_next(comp_item)) {
00648                        ECalComponent *comp = comp_item->data;
00649 
00650                        g_debug("Appending item %p", e_cal_component_get_as_string(comp));
00651 
00652                        ECalComponentVType vtype = e_cal_component_get_vtype (comp);
00653                        if (vtype != E_CAL_COMPONENT_EVENT && vtype != E_CAL_COMPONENT_TODO) return FALSE;
00654 
00655                        icalproperty_status status;
00656                        e_cal_component_get_status (comp, &status);
00657                        if (status == ICAL_STATUS_COMPLETED || status == ICAL_STATUS_CANCELLED) return FALSE;
00658 
00659                        g_object_ref(comp);
00660 
00661                        ECalComponentDateTime datetime;
00662                        icaltimezone *appointment_zone = NULL;
00663                        icaltimezone *current_zone = NULL;
00664 
00665                        if (vtype == E_CAL_COMPONENT_EVENT)
00666                                e_cal_component_get_dtstart (comp, &datetime);
00667                        else
00668                                e_cal_component_get_due (comp, &datetime);
00669 
00670                        appointment_zone = icaltimezone_get_builtin_timezone_from_tzid(datetime.tzid);
00671                        current_zone = icaltimezone_get_builtin_timezone_from_tzid(current_timezone);
00672                        if (!appointment_zone || datetime.value->is_date) { // If it's today put in the current timezone?
00673                                appointment_zone = current_zone;
00674                        }
00675 
00676                        struct comp_instance *ci = comp_instance_new (comp, start, end, E_SOURCE(data));
00677                        comp_instances = g_list_append (comp_instances, ci);
00678                }
00679                return TRUE;
00680        }
00681        return FALSE;
00682 }
00683 
00684 /* Populate the menu with todays, next 5 appointments. 
00685  * we should hook into the ABOUT TO SHOW signal and use that to update the appointments.
00686  * Experience has shown that caldav's and webcals can be slow to load from eds
00687  * this is a problem mainly on the EDS side of things, not ours. 
00688  */
00689 static gboolean
00690 update_appointment_menu_items (gpointer user_data)
00691 {
00692        // FFR: we should take into account short term timers, for instance
00693        // tea timers, pomodoro timers etc... that people may add, this is hinted to in the spec.
00694        g_debug("Update appointments called");
00695        if (calendar == NULL) return FALSE;
00696        if (!g_settings_get_boolean(conf, SETTINGS_SHOW_EVENTS_S)) return FALSE;
00697        if (updating_appointments) return TRUE;
00698        updating_appointments = TRUE;
00699        
00700        time_t curtime = 0, t1 = 0, t2 = 0;
00701        GList *l, *s;
00702        GError *gerror = NULL;
00703        gint i;
00704        gint width = 0, height = 0;
00705        ESourceRegistry * src_registry = NULL;
00706        GList * sources = NULL;
00707 
00708        // Get today & work out query times
00709        time(&curtime);
00710        struct tm *today = localtime(&curtime);
00711        const int mday = today->tm_mday;
00712        const int mon = today->tm_mon;
00713        const int year = today->tm_year;
00714 
00715        int start_month_saved = mon;
00716 
00717        struct tm *start_tm = NULL;
00718        int this_year = today->tm_year + 1900;
00719        int days[12]={31,28,31,30,31,30,31,31,30,31,30,31};
00720        if ((this_year % 400 == 0) || (this_year % 100 > 0 && this_year % 4 == 0)) days[1] = 29;
00721        
00722        int highlightdays = days[mon] - mday + 1;
00723        t1 = curtime; // By default the current time is the appointment start time. 
00724        
00725        if (start_time_appointments > 0) {
00726               start_tm = localtime(&start_time_appointments);
00727               int start_month = start_tm->tm_mon;
00728               start_month_saved = start_month;
00729               int start_year = start_tm->tm_year + 1900;
00730               if ((start_month != mon) || (start_year != this_year)) {
00731                      // Set t1 to the start of that month.
00732                      struct tm month_start = {0};
00733                      month_start.tm_year = start_tm->tm_year;
00734                      month_start.tm_mon = start_tm->tm_mon;
00735                      month_start.tm_mday = 1;
00736                      t1 = mktime(&month_start);
00737                      highlightdays = days[start_month];
00738               }
00739        }
00740        
00741        g_debug("Will highlight %d days from %s", highlightdays, ctime(&t1));
00742 
00743        highlightdays = highlightdays + 7; // Minimum of 7 days ahead 
00744        t2 = t1 + (time_t) (highlightdays * 24 * 60 * 60);
00745        
00746        // clear any previous comp_instances
00747        g_list_free_full (comp_instances, (GDestroyNotify)comp_instance_free);
00748        comp_instances = NULL;
00749 
00750        src_registry = e_source_registry_new_sync (NULL, &gerror);
00751        if (!src_registry) {
00752                g_debug("Failed to get access to source registry: %s\n", gerror->message);
00753                return FALSE;
00754        }
00755 
00756        sources = e_source_registry_list_sources(src_registry, E_SOURCE_EXTENSION_CALENDAR);
00757 
00758        // Generate instances for all sources
00759        for (s = g_list_first (sources); s; s = g_list_next (s)) {
00760 
00761                ESource *source = E_SOURCE (s->data);
00762                g_signal_connect (G_OBJECT(source), "changed", G_CALLBACK (update_appointment_menu_items), NULL);
00763                ECalClient *ecal = e_cal_client_new(source, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, &gerror);
00764 
00765                icaltimezone* current_zone = icaltimezone_get_builtin_timezone(current_timezone);
00766                if (!current_zone) {
00767                        // current_timezone may be a TZID?
00768                        current_zone = icaltimezone_get_builtin_timezone_from_tzid(current_timezone);
00769                }
00770 
00771                e_cal_client_set_default_timezone (ecal, current_zone);
00772 
00773                g_debug("Checking if source %s is enabled", e_source_get_uid(source));
00774                if (e_source_get_enabled (source)) {
00775                        g_debug("source is enabled, generating instances");
00776 
00777                        if (!e_client_open_sync (E_CLIENT (ecal), TRUE, NULL, &gerror)) {
00778                                g_debug("Failed to open source: %s", gerror->message);
00779                             g_clear_error (&gerror);
00780                             g_object_unref(ecal);
00781                             continue;
00782                      }
00783 
00784                        e_cal_client_generate_instances (ecal, t1, t2, NULL,
00785                                                         (ECalRecurInstanceFn) populate_appointment_instances,
00786                                                         (gpointer) source,
00787                                                         NULL);
00788                }
00789                g_object_unref(ecal);
00790        }
00791        g_list_free_full (sources, g_object_unref);
00792 
00793        g_debug("Number of ECalComponents returned: %d", g_list_length(comp_instances));
00794        GList *sorted_comp_instances = g_list_sort(comp_instances, compare_comp_instances);
00795        comp_instances = NULL;
00796        g_debug("Components sorted");
00797        
00798        /* Hiding all of the previous appointments */
00799        if (appointments != NULL) {
00800               g_debug("Hiding old appointments");
00801               GList * appointment;
00802               for (appointment = appointments; appointment != NULL; appointment = g_list_next(appointment)) {
00803                      DbusmenuMenuitem * litem =  DBUSMENU_MENUITEM(appointment->data);
00804                      g_debug("Hiding old appointment: %p", litem);
00805                      // Remove all the existing menu items which are in appointments.
00806                      dbusmenu_menuitem_property_set_bool(DBUSMENU_MENUITEM(litem), DBUSMENU_MENUITEM_PROP_VISIBLE, FALSE);
00807               }
00808        }
00809 
00810        gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
00811        if (width <= 0) width = 12;
00812        if (height <= 0) height = 12;
00813        if (width > 30) width = 12;
00814        if (height > 30) height = 12;
00815        
00816        gchar *time_format_str = g_settings_get_string(conf, SETTINGS_TIME_FORMAT_S);
00817        gint apt_output;
00818        if (g_strcmp0(time_format_str, "12-hour") == 0) {
00819               apt_output = SETTINGS_TIME_12_HOUR;
00820        } else if (g_strcmp0(time_format_str, "24-hour") == 0) {
00821               apt_output = SETTINGS_TIME_24_HOUR;
00822        } else if (is_locale_12h()) {
00823               apt_output = SETTINGS_TIME_12_HOUR;
00824        } else {
00825               apt_output = SETTINGS_TIME_24_HOUR;
00826        }
00827        g_free (time_format_str);
00828        
00829        GVariantBuilder markeddays;
00830        g_variant_builder_init (&markeddays, G_VARIANT_TYPE ("ai"));
00831        
00832        i = 0;
00833        GList * cached_appointment = appointments;
00834        for (l = sorted_comp_instances; l; l = l->next) {
00835               struct comp_instance *ci = l->data;
00836               ECalComponent *ecalcomp = ci->comp;
00837               char right[20];
00838               //const gchar *uri;
00839               DbusmenuMenuitem * item;
00840               
00841               ECalComponentVType vtype = e_cal_component_get_vtype (ecalcomp);
00842               struct tm due_data = {0};
00843               struct tm *due = NULL;
00844               if (vtype == E_CAL_COMPONENT_EVENT) due = localtime_r(&ci->start, &due_data);
00845               else if (vtype == E_CAL_COMPONENT_TODO) due = localtime_r(&ci->end, &due_data);
00846               else continue;
00847               
00848               const int dmday = due->tm_mday;
00849               const int dmon = due->tm_mon;
00850               const int dyear = due->tm_year;
00851               
00852               if (start_month_saved == dmon) {
00853                      // Mark day if our query hasn't hit the next month. 
00854                      g_debug("Adding marked date %s, %d", ctime(&ci->start), dmday);
00855                      g_variant_builder_add (&markeddays, "i", dmday);
00856               }
00857               
00858               // If the appointment time is less than the selected date, 
00859               // don't create an appointment item for it.
00860               if (vtype == E_CAL_COMPONENT_EVENT) {
00861                      if (ci->start < start_time_appointments) continue;
00862               } else if (vtype == E_CAL_COMPONENT_TODO) {
00863                      if (ci->end < start_time_appointments) continue;
00864               }
00865               
00866               if (i >= 5) continue;
00867               i++;
00868 
00869               if (cached_appointment == NULL) {
00870                      g_debug("Create menu item");
00871                      
00872                      item = dbusmenu_menuitem_new();
00873                      dbusmenu_menuitem_property_set       (item, DBUSMENU_MENUITEM_PROP_TYPE, APPOINTMENT_MENUITEM_TYPE);
00874 
00875                      dbusmenu_menuitem_child_add_position (root, item, 2+i);
00876                      appointments = g_list_append (appointments, item); // Keep track of the items here to make them easy to remove
00877               } else {
00878                      item = DBUSMENU_MENUITEM(cached_appointment->data);
00879                      cached_appointment = g_list_next(cached_appointment);
00880 
00881                      /* Remove the icon as we might not replace it on error */
00882                      dbusmenu_menuitem_property_remove(item, APPOINTMENT_MENUITEM_PROP_ICON);
00883 
00884                      /* Remove the activate handler */
00885                      g_signal_handlers_disconnect_matched(G_OBJECT(item), G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK(activate_cb), NULL);
00886               }
00887 
00888               dbusmenu_menuitem_property_set_bool  (item, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE);
00889               dbusmenu_menuitem_property_set_bool  (item, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE);
00890 
00891        
00892         // Label text        
00893               ECalComponentText valuetext;
00894               e_cal_component_get_summary (ecalcomp, &valuetext);
00895               const gchar * summary = valuetext.value;
00896               g_debug("Summary: %s", summary);
00897               dbusmenu_menuitem_property_set (item, APPOINTMENT_MENUITEM_PROP_LABEL, summary);
00898 
00899               gboolean full_day = FALSE;
00900               if (vtype == E_CAL_COMPONENT_EVENT) {
00901                      time_t start = ci->start;
00902                      if (time_add_day(start, 1) == ci->end) {
00903                             full_day = TRUE;
00904                      }
00905               }
00906 
00907               // Due text
00908               if (full_day) {
00909                      struct tm fulldaytime = {0};
00910                      gmtime_r(&ci->start, &fulldaytime);
00911 
00912                      /* TRANSLATORS: This is a strftime string for the day for full day events
00913                         in the menu.  It should most likely be either '%A' for a full text day
00914                         (Wednesday) or '%a' for a shortened one (Wed).  You should only need to
00915                         change for '%a' in the case of langauges with very long day names. */
00916                      strftime(right, 20, _("%A"), &fulldaytime);
00917               } else {
00918                      if (apt_output == SETTINGS_TIME_12_HOUR) {
00919                             if ((mday == dmday) && (mon == dmon) && (year == dyear))
00920                                    strftime(right, 20, _(DEFAULT_TIME_12_FORMAT), due);
00921                             else
00922                                    strftime(right, 20, _(DEFAULT_TIME_12_FORMAT_WITH_DAY), due);
00923                      } else if (apt_output == SETTINGS_TIME_24_HOUR) {
00924                             if ((mday == dmday) && (mon == dmon) && (year == dyear))
00925                                    strftime(right, 20, _(DEFAULT_TIME_24_FORMAT), due);
00926                             else
00927                                    strftime(right, 20, _(DEFAULT_TIME_24_FORMAT_WITH_DAY), due);
00928                      }
00929               }
00930               g_debug("Appointment time: %s, for date %s", right, asctime(due));
00931               dbusmenu_menuitem_property_set (item, APPOINTMENT_MENUITEM_PROP_RIGHT, right);
00932               
00933               // Now we pull out the URI for the calendar event and try to create a URI that'll work when we execute evolution
00934               // FIXME Because the URI stuff is really broken, we're going to open the calendar at todays date instead
00935               //e_cal_component_get_uid(ecalcomp, &uri);
00936               gchar * ad = isodate_from_time_t(mktime(due));
00937               gchar * cmd = g_strconcat("evolution calendar:///?startdate=", ad, NULL);
00938               g_debug("Command to Execute: %s", cmd);
00939               g_signal_connect_data (G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
00940                                      G_CALLBACK(activate_cb), cmd, (GClosureNotify)g_free, 0);
00941               g_free (ad);
00942 
00943        const gchar *color_spec = e_source_selectable_get_color (e_source_get_extension (ci->source, E_SOURCE_EXTENSION_CALENDAR));
00944         g_debug("Colour to use: %s", color_spec);
00945                      
00946               // Draw the correct icon for the appointment type and then tint it using mask fill.
00947               // For now we'll create a circle
00948         if (color_spec != NULL) {
00949               g_debug("Creating a cairo surface: size, %d by %d", width, height);         
00950               cairo_surface_t *surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, width, height ); 
00951                      cairo_t *cr = cairo_create(surface);
00952               GdkRGBA rgba;
00953               if (gdk_rgba_parse (&rgba, color_spec))
00954                      gdk_cairo_set_source_rgba (cr, &rgba);
00955                      cairo_paint(cr);
00956               cairo_set_source_rgba(cr, 0,0,0,0.5);
00957               cairo_set_line_width(cr, 1);
00958               cairo_rectangle (cr, 0.5, 0.5, width-1, height-1);
00959               cairo_stroke(cr);
00960                      // Convert to pixbuf, in gtk3 this is done with gdk_pixbuf_get_from_surface
00961                      cairo_content_t content = cairo_surface_get_content (surface) | CAIRO_CONTENT_COLOR;
00962                      GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, 
00963                                                          !!(content & CAIRO_CONTENT_ALPHA), 
00964                                                          8, width, height);
00965                      if (pixbuf != NULL) {               
00966                             gint sstride = cairo_image_surface_get_stride( surface ); 
00967                             gint dstride = gdk_pixbuf_get_rowstride (pixbuf);
00968                             guchar *spixels = cairo_image_surface_get_data( surface );
00969                             guchar *dpixels = gdk_pixbuf_get_pixels (pixbuf);
00970 
00971                             int x, y;
00972                             for (y = 0; y < height; y++) {
00973                                    guint32 *src = (guint32 *) spixels;
00974 
00975                                    for (x = 0; x < width; x++) {
00976                                           guint alpha = src[x] >> 24;
00977 
00978                                           if (alpha == 0) {
00979                                           dpixels[x * 4 + 0] = 0;
00980                                           dpixels[x * 4 + 1] = 0;
00981                                           dpixels[x * 4 + 2] = 0;
00982                                    } else {
00983                                                  dpixels[x * 4 + 0] = (((src[x] & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
00984                                                  dpixels[x * 4 + 1] = (((src[x] & 0x00ff00) >>  8) * 255 + alpha / 2) / alpha;
00985                                                  dpixels[x * 4 + 2] = (((src[x] & 0x0000ff) >>  0) * 255 + alpha / 2) / alpha;
00986                                           }
00987                                           dpixels[x * 4 + 3] = alpha;
00988                                    }
00989                                    spixels += sstride;
00990                                    dpixels += dstride;
00991                             }
00992                             
00993                             dbusmenu_menuitem_property_set_image (item, APPOINTMENT_MENUITEM_PROP_ICON, pixbuf);
00994                             g_clear_object (&pixbuf);
00995                      } else {
00996                             g_debug("Creating pixbuf from surface failed");
00997                      }
00998                      cairo_surface_destroy (surface);
00999                      cairo_destroy(cr);
01000               }
01001               g_debug("Adding appointment: %p", item);
01002        }
01003        
01004        g_clear_error (&gerror);
01005 
01006        g_list_free_full (sorted_comp_instances, (GDestroyNotify)comp_instance_free);
01007        sorted_comp_instances = NULL;
01008        
01009        GVariant * marks = g_variant_builder_end (&markeddays);
01010        dbusmenu_menuitem_property_set_variant (calendar, CALENDAR_MENUITEM_PROP_MARKS, marks);
01011 
01012        g_clear_object (&sources);
01013        
01014        updating_appointments = FALSE;
01015        g_debug("End of objects");
01016        return TRUE;
01017 }
01018 
01019 /* Looks for the time and date admin application and enables the
01020    item we have one */
01021 static gboolean
01022 check_for_timeadmin (gpointer user_data)
01023 {
01024        g_return_val_if_fail (settings != NULL, FALSE);
01025 
01026        gchar * timeadmin = g_find_program_in_path("gnome-control-center");
01027        if (timeadmin != NULL) {
01028               g_debug("Found the gnome-control-center application: %s", timeadmin);
01029               dbusmenu_menuitem_property_set_bool(settings, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE);
01030               g_free(timeadmin);
01031        } else {
01032               g_debug("Unable to find gnome-control-center app.");
01033               dbusmenu_menuitem_property_set_bool(settings, DBUSMENU_MENUITEM_PROP_ENABLED, FALSE);
01034        }
01035 
01036        return FALSE;
01037 }
01038 
01039 static void
01040 show_locations_changed (void)
01041 {
01042        /* Re-calculate */
01043        update_location_menu_items();
01044 }
01045 
01046 static void
01047 time_format_changed (void)
01048 {
01049        update_appointment_menu_items(NULL);
01050 }
01051 
01052 /* Does the work to build the default menu, really calls out
01053    to other functions but this is the core to clean up the
01054    main function. */
01055 static void
01056 build_menus (DbusmenuMenuitem * root)
01057 {
01058        g_debug("Building Menus.");
01059        if (date == NULL) {
01060               date = dbusmenu_menuitem_new();
01061               dbusmenu_menuitem_property_set     (date, DBUSMENU_MENUITEM_PROP_LABEL, _("No date yet…"));
01062               dbusmenu_menuitem_property_set_bool(date, DBUSMENU_MENUITEM_PROP_ENABLED, FALSE);
01063               dbusmenu_menuitem_child_append(root, date);
01064 
01065               g_idle_add(update_datetime, NULL);
01066        }
01067 
01068        if (calendar == NULL) {
01069               calendar = dbusmenu_menuitem_new();
01070               dbusmenu_menuitem_property_set (calendar, DBUSMENU_MENUITEM_PROP_TYPE, DBUSMENU_CALENDAR_MENUITEM_TYPE);
01071               /* insensitive until we check for available apps */
01072               dbusmenu_menuitem_property_set_bool(calendar, DBUSMENU_MENUITEM_PROP_ENABLED, FALSE);
01073               g_signal_connect (G_OBJECT(calendar), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
01074                                             G_CALLBACK (activate_cb), "evolution -c calendar");
01075               dbusmenu_menuitem_child_append(root, calendar);
01076 
01077               g_idle_add(check_for_calendar, NULL);
01078        }
01079 
01080        if (!get_greeter_mode ()) {
01081               locations_separator = dbusmenu_menuitem_new();
01082               dbusmenu_menuitem_property_set(locations_separator, DBUSMENU_MENUITEM_PROP_TYPE, DBUSMENU_CLIENT_TYPES_SEPARATOR);
01083               dbusmenu_menuitem_property_set_bool (locations_separator, DBUSMENU_MENUITEM_PROP_VISIBLE, FALSE);
01084               dbusmenu_menuitem_child_append(root, locations_separator);
01085 
01086               update_location_menu_items();
01087        
01088               g_signal_connect (conf, "changed::" SETTINGS_SHOW_LOCATIONS_S, G_CALLBACK (show_locations_changed), NULL);
01089               g_signal_connect (conf, "changed::" SETTINGS_SHOW_DETECTED_S, G_CALLBACK (show_locations_changed), NULL);
01090               g_signal_connect (conf, "changed::" SETTINGS_LOCATIONS_S, G_CALLBACK (show_locations_changed), NULL);
01091               g_signal_connect (conf, "changed::" SETTINGS_SHOW_EVENTS_S, G_CALLBACK (show_events_changed), NULL);
01092               g_signal_connect (conf, "changed::" SETTINGS_TIME_FORMAT_S, G_CALLBACK (time_format_changed), NULL);
01093 
01094               DbusmenuMenuitem * separator = dbusmenu_menuitem_new();
01095               dbusmenu_menuitem_property_set(separator, DBUSMENU_MENUITEM_PROP_TYPE, DBUSMENU_CLIENT_TYPES_SEPARATOR);
01096               dbusmenu_menuitem_child_append(root, separator);
01097 
01098               settings = dbusmenu_menuitem_new();
01099               dbusmenu_menuitem_property_set     (settings, DBUSMENU_MENUITEM_PROP_LABEL, _("Time & Date Settings…"));
01100               /* insensitive until we check for available apps */
01101               dbusmenu_menuitem_property_set_bool(settings, DBUSMENU_MENUITEM_PROP_ENABLED, FALSE);
01102               g_signal_connect(G_OBJECT(settings), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, G_CALLBACK(activate_cb), SETTINGS_APP_INVOCATION);
01103               dbusmenu_menuitem_child_append(root, settings);
01104               g_idle_add(check_for_timeadmin, NULL);
01105        }
01106 
01107        return;
01108 }
01109 
01110 /* Run when the timezone file changes */
01111 static void
01112 timezone_changed (GFileMonitor * monitor, GFile * file, GFile * otherfile, GFileMonitorEvent event, gpointer user_data)
01113 {
01114        update_current_timezone();
01115        datetime_interface_update(DATETIME_INTERFACE(user_data));
01116        update_datetime(NULL);
01117        setup_timer();
01118        return;
01119 }
01120 
01121 /* Set up monitoring the timezone file */
01122 static void
01123 build_timezone (DatetimeInterface * dbus)
01124 {
01125        GFile * timezonefile = g_file_new_for_path(TIMEZONE_FILE);
01126        GFileMonitor * monitor = g_file_monitor_file(timezonefile, G_FILE_MONITOR_NONE, NULL, NULL);
01127        if (monitor != NULL) {
01128               g_signal_connect(G_OBJECT(monitor), "changed", G_CALLBACK(timezone_changed), dbus);
01129               g_debug("Monitoring timezone file: '" TIMEZONE_FILE "'");
01130        } else {
01131               g_warning("Unable to monitor timezone file: '" TIMEZONE_FILE "'");
01132        }
01133        return;
01134 }
01135 
01136 /* Source ID for the timer */
01137 static guint timer = 0;
01138 
01139 /* Execute at a given time, update and setup a new
01140    timer to go again.  */
01141 static gboolean
01142 timer_func (gpointer user_data)
01143 {
01144        timer = 0;
01145        /* Reset up each time to reduce error */
01146        setup_timer();
01147        update_datetime(NULL);
01148        return FALSE;
01149 }
01150 
01151 /* Sets up the time to launch the timer to update the
01152    date in the datetime entry */
01153 static void
01154 setup_timer (void)
01155 {
01156        if (timer != 0) {
01157               g_source_remove(timer);
01158               timer = 0;
01159        }
01160 
01161        time_t t;
01162        t = time(NULL);
01163        struct tm * ltime = localtime(&t);
01164 
01165        timer = g_timeout_add_seconds(((23 - ltime->tm_hour) * 60 * 60) +
01166                                      ((59 - ltime->tm_min) * 60) +
01167                                      ((60 - ltime->tm_sec)) + 60 /* one minute past */,
01168                                      timer_func, NULL);
01169 
01170        return;
01171 }
01172 
01173 static void
01174 session_active_change_cb (GDBusProxy * proxy, gchar * sender_name, gchar * signal_name,
01175                           GVariant * parameters, gpointer user_data)
01176 {
01177        // Just returned from suspend
01178        if (g_strcmp0(signal_name, "SystemIdleHintChanged") == 0) {
01179               gboolean idle = FALSE;
01180               g_variant_get(parameters, "(b)", &idle);
01181               if (!idle) {
01182                      datetime_interface_update(DATETIME_INTERFACE(user_data));
01183                      update_datetime(NULL);
01184                      setup_timer();
01185               }
01186        }
01187        return;
01188 }
01189 
01190 /* for hooking into console kit signal on wake from suspend */
01191 static void
01192 system_proxy_cb (GObject * object, GAsyncResult * res, gpointer user_data)
01193 {
01194        GError * error = NULL;
01195        
01196        GDBusProxy * proxy = g_dbus_proxy_new_for_bus_finish(res, &error);
01197 
01198        if (error != NULL) {
01199               g_warning("Could not grab DBus proxy for ConsoleKit: %s", error->message);
01200               g_clear_error (&error);
01201               return;
01202        }
01203 
01204        g_signal_connect(proxy, "g-signal", G_CALLBACK(session_active_change_cb), user_data);
01205 }
01206 
01207 /* Callback from getting the address */
01208 static void
01209 geo_address_cb (GeoclueAddress * address, int timestamp, GHashTable * addy_data, GeoclueAccuracy * accuracy, GError * error, gpointer user_data)
01210 {
01211        if (error != NULL) {
01212               g_warning("Unable to get Geoclue address: %s", error->message);
01213               g_clear_error (&error);
01214               return;
01215        }
01216 
01217        g_debug("Geoclue timezone is: %s", (gchar *)g_hash_table_lookup(addy_data, "timezone"));
01218 
01219        if (geo_timezone != NULL) {
01220               g_free(geo_timezone);
01221               geo_timezone = NULL;
01222        }
01223 
01224        gpointer tz_hash = g_hash_table_lookup(addy_data, "timezone");
01225        if (tz_hash != NULL) {
01226               geo_timezone = g_strdup((gchar *)tz_hash);
01227        }
01228 
01229        update_location_menu_items();
01230 
01231        return;
01232 }
01233 
01234 /* Clean up the reference we kept to the address and make sure to
01235    drop the signals incase someone else has one. */
01236 static void
01237 geo_address_clean (void)
01238 {
01239        if (geo_address == NULL) {
01240               return;
01241        }
01242 
01243        g_signal_handlers_disconnect_by_func(G_OBJECT(geo_address), geo_address_cb, NULL);
01244        g_object_unref(G_OBJECT(geo_address));
01245 
01246        geo_address = NULL;
01247 
01248        return;
01249 }
01250 
01251 /* Clean up and remove all signal handlers from the client as we
01252    unreference it as well. */
01253 static void
01254 geo_client_clean (void)
01255 {
01256        if (geo_master == NULL) {
01257               return;
01258        }
01259 
01260        g_signal_handlers_disconnect_by_func(G_OBJECT(geo_master), geo_client_invalid, NULL);
01261        g_signal_handlers_disconnect_by_func(G_OBJECT(geo_master), geo_address_change, NULL);
01262        g_object_unref(G_OBJECT(geo_master));
01263 
01264        geo_master = NULL;
01265 
01266        return;
01267 }
01268 
01269 /* Callback from creating the address */
01270 static void
01271 geo_create_address (GeoclueMasterClient * master, GeoclueAddress * address, GError * error, gpointer user_data)
01272 {
01273        if (error != NULL) {
01274               g_warning("Unable to create GeoClue address: %s", error->message);
01275               g_clear_error (&error);
01276               return;
01277        }
01278 
01279        /* We shouldn't have created a new address if we already had one
01280           so this is a warning.  But, it really is only a mem-leak so we
01281           don't need to error out. */
01282        g_warn_if_fail(geo_address == NULL);
01283        geo_address_clean();
01284 
01285        g_debug("Created Geoclue Address");
01286        geo_address = address;
01287        g_object_ref(G_OBJECT(geo_address));
01288 
01289        geoclue_address_get_address_async(geo_address, geo_address_cb, NULL);
01290 
01291        g_signal_connect(G_OBJECT(address), "address-changed", G_CALLBACK(geo_address_cb), NULL);
01292 
01293        return;
01294 }
01295 
01296 /* Callback from setting requirements */
01297 static void
01298 geo_req_set (GeoclueMasterClient * master, GError * error, gpointer user_data)
01299 {
01300        if (error != NULL) {
01301               g_warning("Unable to set Geoclue requirements: %s", error->message);
01302               g_clear_error (&error);
01303        }
01304        return;
01305 }
01306 
01307 /* Client is killing itself rather oddly */
01308 static void
01309 geo_client_invalid (GeoclueMasterClient * client, gpointer user_data)
01310 {
01311        g_warning("Master client invalid, rebuilding.");
01312 
01313        /* Client changes we can assume the address is now invalid so we
01314           need to unreference the one we had. */
01315        geo_address_clean();
01316 
01317        /* And our master client is invalid */
01318        geo_client_clean();
01319 
01320        GeoclueMaster * master = geoclue_master_get_default();
01321        geoclue_master_create_client_async(master, geo_create_client, NULL);
01322 
01323        if (geo_timezone != NULL) {
01324               g_free(geo_timezone);
01325               geo_timezone = NULL;
01326        }
01327 
01328        update_location_menu_items();
01329 
01330        return;
01331 }
01332 
01333 /* Address provider changed, we need to get that one */
01334 static void
01335 geo_address_change (GeoclueMasterClient * client, gchar * a, gchar * b, gchar * c, gchar * d, gpointer user_data)
01336 {
01337        g_warning("Address provider changed.  Let's change");
01338 
01339        /* If the address is supposed to have changed we need to drop the old
01340           address before starting to get the new one. */
01341        geo_address_clean();
01342 
01343        geoclue_master_client_create_address_async(geo_master, geo_create_address, NULL);
01344 
01345        if (geo_timezone != NULL) {
01346               g_free(geo_timezone);
01347               geo_timezone = NULL;
01348        }
01349 
01350        update_location_menu_items();
01351 
01352        return;
01353 }
01354 
01355 /* Callback from creating the client */
01356 static void
01357 geo_create_client (GeoclueMaster * master, GeoclueMasterClient * client, gchar * path, GError * error, gpointer user_data)
01358 {
01359        g_debug("Created Geoclue client at: %s", path);
01360 
01361        geo_master = client;
01362 
01363        if (error != NULL) {
01364               g_warning("Unable to get a GeoClue client!  '%s'  Geolocation based timezone support will not be available.", error->message);
01365               g_clear_error (&error);
01366               return;
01367        }
01368 
01369        if (geo_master == NULL) {
01370               g_warning(_("Unable to get a GeoClue client!  Geolocation based timezone support will not be available."));
01371               return;
01372        }
01373 
01374        g_object_ref(G_OBJECT(geo_master));
01375 
01376        /* New client, make sure we don't have an address hanging on */
01377        geo_address_clean();
01378 
01379        geoclue_master_client_set_requirements_async(geo_master,
01380                                                     GEOCLUE_ACCURACY_LEVEL_REGION,
01381                                                     0,
01382                                                     FALSE,
01383                                                     GEOCLUE_RESOURCE_ALL,
01384                                                     geo_req_set,
01385                                                     NULL);
01386 
01387        geoclue_master_client_create_address_async(geo_master, geo_create_address, NULL);
01388 
01389        g_signal_connect(G_OBJECT(client), "invalidated", G_CALLBACK(geo_client_invalid), NULL);
01390        g_signal_connect(G_OBJECT(client), "address-provider-changed", G_CALLBACK(geo_address_change), NULL);
01391 
01392        return;
01393 }
01394 
01395 static gboolean
01396 get_greeter_mode (void)
01397 {
01398   const gchar *var;
01399   var = g_getenv("INDICATOR_GREETER_MODE");
01400   return (g_strcmp0(var, "1") == 0);
01401 }
01402 
01403 /* Repsonds to the service object saying it's time to shutdown.
01404    It stops the mainloop. */
01405 static void 
01406 service_shutdown (IndicatorService * service, gpointer user_data)
01407 {
01408        g_warning("Shutting down service!");
01409        g_main_loop_quit(mainloop);
01410        return;
01411 }
01412 
01413 /* Function to build everything up.  Entry point from asm. */
01414 int
01415 main (int argc, char ** argv)
01416 {
01417        g_type_init();
01418 
01419        /* Acknowledging the service init and setting up the interface */
01420        service = indicator_service_new_version(SERVICE_NAME, SERVICE_VERSION);
01421        g_signal_connect(service, INDICATOR_SERVICE_SIGNAL_SHUTDOWN, G_CALLBACK(service_shutdown), NULL);
01422 
01423        /* Setting up i18n and gettext.  Apparently, we need
01424           all of these. */
01425        setlocale (LC_ALL, "");
01426        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
01427        textdomain (GETTEXT_PACKAGE);
01428 
01429        /* Set up GSettings */
01430        conf = g_settings_new(SETTINGS_INTERFACE);
01431        /* Set up gconf for getting evolution enabled calendars */
01432        gconf = gconf_client_get_default();
01433        // TODO Add a signal handler to catch gsettings changes and respond to them
01434 
01435        /* Building the base menu */
01436        server = dbusmenu_server_new(MENU_OBJ);
01437        root = dbusmenu_menuitem_new();
01438        dbusmenu_server_set_root(server, root);
01439        
01440        build_menus(root);
01441        
01442        /* Cache the timezone */
01443        update_current_timezone();
01444 
01445        /* Setup geoclue */
01446        GeoclueMaster * master = geoclue_master_get_default();
01447        geoclue_master_create_client_async(master, geo_create_client, NULL);
01448 
01449        /* Setup dbus interface */
01450        dbus = g_object_new(DATETIME_INTERFACE_TYPE, NULL);
01451 
01452        /* Setup timezone watch */
01453        build_timezone(dbus);
01454 
01455        /* Setup the timer */
01456        setup_timer();
01457 
01458        /* And watch for system resumes */
01459        g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
01460                                 G_DBUS_PROXY_FLAGS_NONE,
01461                                 NULL,
01462                                 "org.freedesktop.ConsoleKit",
01463                                 "/org/freedesktop/ConsoleKit/Manager",
01464                                 "org.freedesktop.ConsoleKit.Manager",
01465                                 NULL, system_proxy_cb, dbus);
01466 
01467        mainloop = g_main_loop_new(NULL, FALSE);
01468        g_main_loop_run(mainloop);
01469 
01470        g_object_unref(G_OBJECT(conf));
01471        g_object_unref(G_OBJECT(master));
01472        g_object_unref(G_OBJECT(dbus));
01473        g_object_unref(G_OBJECT(service));
01474        g_object_unref(G_OBJECT(server));
01475        g_object_unref(G_OBJECT(root));
01476 
01477        icaltimezone_free_builtin_timezones();
01478 
01479        geo_address_clean();
01480        geo_client_clean();
01481 
01482        return 0;
01483 }