#ifdef HAVE_CONFIG_H #include <config.h> #endif #include <dirent.h> #include <errno.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <time.h> #include <unistd.h> #include <math.h> #include <glib.h> #include <gio/gio.h> #include <gtk/gtk.h> #include "clock-location.h" #include "clock-marshallers.h" #include "set-timezone.h" #include "system-timezone.h" #if GTK_CHECK_VERSION (3, 0, 0) #define gtk_vbox_new(X, Y) gtk_box_new(GTK_ORIENTATION_VERTICAL, Y) #define gtk_hbox_new(X, Y) gtk_box_new(GTK_ORIENTATION_HORIZONTAL, Y) #endif G_DEFINE_TYPE (ClockLocation, clock_location, G_TYPE_OBJECT) typedef struct { gchar *name; gchar *city; SystemTimezone *systz; gchar *timezone; gchar *tzname; gfloat latitude; gfloat longitude; gchar *weather_code; WeatherInfo *weather_info; guint weather_timeout; guint weather_retry_time; TempUnit temperature_unit; SpeedUnit speed_unit; } ClockLocationPrivate; #define WEATHER_TIMEOUT_BASE 30 #define WEATHER_TIMEOUT_MAX 1800 #define WEATHER_EMPTY_CODE "-" enum { WEATHER_UPDATED, SET_CURRENT, LAST_SIGNAL }; static guint location_signals[LAST_SIGNAL] = { 0 }; static void clock_location_finalize (GObject *); static void clock_location_set_tz (ClockLocation *this); static void clock_location_unset_tz (ClockLocation *this); static gboolean update_weather_info (ClockLocation *loc); static void setup_weather_updates (ClockLocation *loc); static gchar *clock_location_get_valid_weather_code (const gchar *code); #define PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CLOCK_LOCATION_TYPE, ClockLocationPrivate)) ClockLocation * clock_location_find_and_ref (GList *locations, const gchar *name, const gchar *city, const gchar *timezone, gfloat latitude, gfloat longitude, const gchar *code) { GList *l; ClockLocationPrivate *priv; for (l = locations; l != NULL; l = l->next) { priv = PRIVATE (l->data); if (priv->latitude == latitude && priv->longitude == longitude && g_strcmp0 (priv->weather_code, code) == 0 && g_strcmp0 (priv->timezone, timezone) == 0 && g_strcmp0 (priv->city, city) == 0 && g_strcmp0 (priv->name, name) == 0) break; } if (l != NULL) return g_object_ref (CLOCK_LOCATION (l->data)); else return NULL; } ClockLocation * clock_location_new (const gchar *name, const gchar *city, const gchar *timezone, gfloat latitude, gfloat longitude, const gchar *code, WeatherPrefs *prefs) { ClockLocation *this; ClockLocationPrivate *priv; this = g_object_new (CLOCK_LOCATION_TYPE, NULL); priv = PRIVATE (this); priv->name = g_strdup (name); priv->city = g_strdup (city); priv->timezone = g_strdup (timezone); /* initialize priv->tzname */ clock_location_set_tz (this); clock_location_unset_tz (this); priv->latitude = latitude; priv->longitude = longitude; priv->weather_code = clock_location_get_valid_weather_code (code); if (prefs) { priv->temperature_unit = prefs->temperature_unit; priv->speed_unit = prefs->speed_unit; } setup_weather_updates (this); return this; } static ClockLocation *current_location = NULL; static void clock_location_class_init (ClockLocationClass *this_class) { GObjectClass *g_obj_class = G_OBJECT_CLASS (this_class); g_obj_class->finalize = clock_location_finalize; location_signals[WEATHER_UPDATED] = g_signal_new ("weather-updated", G_OBJECT_CLASS_TYPE (g_obj_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (ClockLocationClass, weather_updated), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); location_signals[SET_CURRENT] = g_signal_new ("set-current", G_OBJECT_CLASS_TYPE (g_obj_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (ClockLocationClass, set_current), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_type_class_add_private (this_class, sizeof (ClockLocationPrivate)); } static void network_changed (GNetworkMonitor *monitor, gboolean available, ClockLocation *loc) { ClockLocationPrivate *priv = PRIVATE (loc); if (available) { priv->weather_retry_time = WEATHER_TIMEOUT_BASE; update_weather_info (loc); } } static void clock_location_init (ClockLocation *this) { ClockLocationPrivate *priv = PRIVATE (this); GNetworkMonitor *monitor; priv->name = NULL; priv->city = NULL; priv->systz = system_timezone_new (); priv->timezone = NULL; priv->tzname = NULL; priv->latitude = 0; priv->longitude = 0; monitor = g_network_monitor_get_default(); g_signal_connect (monitor, "network-changed", G_CALLBACK (network_changed), this); priv->temperature_unit = TEMP_UNIT_CENTIGRADE; priv->speed_unit = SPEED_UNIT_MS; } static void clock_location_finalize (GObject *g_obj) { ClockLocationPrivate *priv = PRIVATE (g_obj); GNetworkMonitor *monitor; monitor = g_network_monitor_get_default (); g_signal_handlers_disconnect_by_func (monitor, G_CALLBACK (network_changed), CLOCK_LOCATION (g_obj)); if (priv->name) { g_free (priv->name); priv->name = NULL; } if (priv->city) { g_free (priv->city); priv->city = NULL; } if (priv->systz) { g_object_unref (priv->systz); priv->systz = NULL; } if (priv->timezone) { g_free (priv->timezone); priv->timezone = NULL; } if (priv->tzname) { g_free (priv->tzname); priv->tzname = NULL; } if (priv->weather_code) { g_free (priv->weather_code); priv->weather_code = NULL; } if (priv->weather_info) { weather_info_free (priv->weather_info); priv->weather_info = NULL; } if (priv->weather_timeout) { g_source_remove (priv->weather_timeout); priv->weather_timeout = 0; } G_OBJECT_CLASS (clock_location_parent_class)->finalize (g_obj); } const gchar * clock_location_get_display_name (ClockLocation *loc) { ClockLocationPrivate *priv = PRIVATE (loc); if (priv->name && priv->name[0]) return priv->name; else return priv->city; } const gchar * clock_location_get_name (ClockLocation *loc) { ClockLocationPrivate *priv = PRIVATE (loc); return priv->name; } void clock_location_set_name (ClockLocation *loc, const gchar *name) { ClockLocationPrivate *priv = PRIVATE (loc); if (priv->name) { g_free (priv->name); priv->name = NULL; } priv->name = g_strdup (name); } const gchar * clock_location_get_city (ClockLocation *loc) { ClockLocationPrivate *priv = PRIVATE (loc); return priv->city; } void clock_location_set_city (ClockLocation *loc, const gchar *city) { ClockLocationPrivate *priv = PRIVATE (loc); if (priv->city) { g_free (priv->city); priv->city = NULL; } priv->city = g_strdup (city); } gchar * clock_location_get_timezone (ClockLocation *loc) { ClockLocationPrivate *priv = PRIVATE (loc); return priv->timezone; } void clock_location_set_timezone (ClockLocation *loc, const gchar *timezone) { ClockLocationPrivate *priv = PRIVATE (loc); if (priv->timezone) { g_free (priv->timezone); priv->timezone = NULL; } priv->timezone = g_strdup (timezone); } gchar * clock_location_get_tzname (ClockLocation *loc) { ClockLocationPrivate *priv = PRIVATE (loc); return priv->tzname; } void clock_location_get_coords (ClockLocation *loc, gfloat *latitude, gfloat *longitude) { ClockLocationPrivate *priv = PRIVATE (loc); *latitude = priv->latitude; *longitude = priv->longitude; } void clock_location_set_coords (ClockLocation *loc, gfloat latitude, gfloat longitude) { ClockLocationPrivate *priv = PRIVATE (loc); priv->latitude = latitude; priv->longitude = longitude; } static void clock_location_set_tzname (ClockLocation *this, const char *tzname) { ClockLocationPrivate *priv = PRIVATE (this); if (priv->tzname) { if (strcmp (priv->tzname, tzname) == 0) { return; } g_free (priv->tzname); priv->tzname = NULL; } if (tzname) { priv->tzname = g_strdup (tzname); } else { priv->tzname = NULL; } } static void clock_location_set_tz (ClockLocation *this) { ClockLocationPrivate *priv = PRIVATE (this); time_t now_t; struct tm now; if (priv->timezone == NULL) { return; } setenv ("TZ", priv->timezone, 1); tzset(); now_t = time (NULL); localtime_r (&now_t, &now); if (now.tm_isdst > 0) { clock_location_set_tzname (this, tzname[1]); } else { clock_location_set_tzname (this, tzname[0]); } } static void clock_location_unset_tz (ClockLocation *this) { ClockLocationPrivate *priv = PRIVATE (this); const char *env_timezone; if (priv->timezone == NULL) { return; } env_timezone = system_timezone_get_env (priv->systz); if (env_timezone) { setenv ("TZ", env_timezone, 1); } else { unsetenv ("TZ"); } tzset(); } void clock_location_localtime (ClockLocation *loc, struct tm *tm) { time_t now; clock_location_set_tz (loc); time (&now); localtime_r (&now, tm); clock_location_unset_tz (loc); } gboolean clock_location_is_current_timezone (ClockLocation *loc) { ClockLocationPrivate *priv = PRIVATE (loc); const char *zone; zone = system_timezone_get (priv->systz); if (zone) return strcmp (zone, priv->timezone) == 0; else return clock_location_get_offset (loc) == 0; } gboolean clock_location_is_current (ClockLocation *loc) { if (current_location == loc) return TRUE; else if (current_location != NULL) return FALSE; if (clock_location_is_current_timezone (loc)) { /* Note that some code in clock.c depends on the fact that * calling this function can set the current location if * there's none */ current_location = loc; g_object_add_weak_pointer (G_OBJECT (current_location), (gpointer *)¤t_location); g_signal_emit (current_location, location_signals[SET_CURRENT], 0, NULL); return TRUE; } return FALSE; } glong clock_location_get_offset (ClockLocation *loc) { ClockLocationPrivate *priv = PRIVATE (loc); glong sys_timezone, local_timezone; glong offset; time_t t; struct tm *tm; t = time (NULL); unsetenv ("TZ"); tm = localtime (&t); sys_timezone = timezone; if (tm->tm_isdst > 0) { sys_timezone -= 3600; } setenv ("TZ", priv->timezone, 1); tm = localtime (&t); local_timezone = timezone; if (tm->tm_isdst > 0) { local_timezone -= 3600; } offset = local_timezone - sys_timezone; clock_location_unset_tz (loc); return offset; } typedef struct { ClockLocation *location; GFunc callback; gpointer data; GDestroyNotify destroy; } MakeCurrentData; static void make_current_cb (gpointer data, GError *error) { MakeCurrentData *mcdata = data; if (error == NULL) { if (current_location) g_object_remove_weak_pointer (G_OBJECT (current_location), (gpointer *)¤t_location); current_location = mcdata->location; g_object_add_weak_pointer (G_OBJECT (current_location), (gpointer *)¤t_location); g_signal_emit (current_location, location_signals[SET_CURRENT], 0, NULL); } if (mcdata->callback) mcdata->callback (mcdata->data, error); else g_error_free (error); } static void free_make_current_data (gpointer data) { MakeCurrentData *mcdata = data; if (mcdata->destroy) mcdata->destroy (mcdata->data); g_object_unref (mcdata->location); g_free (mcdata); } void clock_location_make_current (ClockLocation *loc, GFunc callback, gpointer data, GDestroyNotify destroy) { ClockLocationPrivate *priv = PRIVATE (loc); gchar *filename; MakeCurrentData *mcdata; if (loc == current_location) { if (destroy) destroy (data); return; } if (clock_location_is_current_timezone (loc)) { if (current_location) g_object_remove_weak_pointer (G_OBJECT (current_location), (gpointer *)¤t_location); current_location = loc; g_object_add_weak_pointer (G_OBJECT (current_location), (gpointer *)¤t_location); g_signal_emit (current_location, location_signals[SET_CURRENT], 0, NULL); if (callback) callback (data, NULL); if (destroy) destroy (data); return; } mcdata = g_new (MakeCurrentData, 1); mcdata->location = g_object_ref (loc); mcdata->callback = callback; mcdata->data = data; mcdata->destroy = destroy; filename = g_build_filename (SYSTEM_ZONEINFODIR, priv->timezone, NULL); set_system_timezone_async (filename, (GFunc)make_current_cb, mcdata, free_make_current_data); g_free (filename); } static gchar * clock_location_get_valid_weather_code (const gchar *code) { if (!code || code[0] == '\0') return g_strdup (WEATHER_EMPTY_CODE); else return g_strdup (code); } const gchar * clock_location_get_weather_code (ClockLocation *loc) { ClockLocationPrivate *priv = PRIVATE (loc); return priv->weather_code; } void clock_location_set_weather_code (ClockLocation *loc, const gchar *code) { ClockLocationPrivate *priv = PRIVATE (loc); g_free (priv->weather_code); priv->weather_code = clock_location_get_valid_weather_code (code); setup_weather_updates (loc); } WeatherInfo * clock_location_get_weather_info (ClockLocation *loc) { ClockLocationPrivate *priv = PRIVATE (loc); return priv->weather_info; } static void set_weather_update_timeout (ClockLocation *loc) { ClockLocationPrivate *priv = PRIVATE (loc); guint timeout; if (!weather_info_network_error (priv->weather_info)) { /* The last update succeeded; set the next update to * happen in half an hour, and reset the retry timer. */ timeout = WEATHER_TIMEOUT_MAX; priv->weather_retry_time = WEATHER_TIMEOUT_BASE; } else { /* The last update failed; set the next update * according to the retry timer, and exponentially * back off the retry timer. */ timeout = priv->weather_retry_time; priv->weather_retry_time *= 2; if (priv->weather_retry_time > WEATHER_TIMEOUT_MAX) priv->weather_retry_time = WEATHER_TIMEOUT_MAX; } if (priv->weather_timeout) g_source_remove (priv->weather_timeout); priv->weather_timeout = g_timeout_add_seconds (timeout, update_weather_info, loc); } static void weather_info_updated (WeatherInfo *info, gpointer data) { ClockLocation *loc = data; ClockLocationPrivate *priv = PRIVATE (loc); set_weather_update_timeout (loc); g_signal_emit (loc, location_signals[WEATHER_UPDATED], 0, priv->weather_info); } static gboolean update_weather_info (ClockLocation *loc) { ClockLocationPrivate *priv = PRIVATE (loc); WeatherPrefs prefs = { FORECAST_STATE, FALSE, NULL, TEMP_UNIT_CENTIGRADE, SPEED_UNIT_MS, PRESSURE_UNIT_MB, DISTANCE_UNIT_KM }; // set temperature and speed units only if different from // invalid/default if (priv->temperature_unit > TEMP_UNIT_DEFAULT) prefs.temperature_unit = priv->temperature_unit; if (priv->speed_unit > SPEED_UNIT_DEFAULT) prefs.speed_unit = priv->speed_unit; weather_info_abort (priv->weather_info); weather_info_update (priv->weather_info, &prefs, weather_info_updated, loc); return TRUE; } static gchar * rad2dms (gfloat lat, gfloat lon) { gchar h, h2; gfloat d, deg, min, d2, deg2, min2; h = lat > 0 ? 'N' : 'S'; d = fabs (lat); deg = floor (d); min = floor (60 * (d - deg)); h2 = lon > 0 ? 'E' : 'W'; d2 = fabs (lon); deg2 = floor (d2); min2 = floor (60 * (d2 - deg2)); return g_strdup_printf ("%02d-%02d%c %02d-%02d%c", (int)deg, (int)min, h, (int)deg2, (int)min2, h2); } static void setup_weather_updates (ClockLocation *loc) { ClockLocationPrivate *priv = PRIVATE (loc); WeatherLocation *wl; WeatherPrefs prefs = { FORECAST_STATE, FALSE, NULL, TEMP_UNIT_CENTIGRADE, SPEED_UNIT_MS, PRESSURE_UNIT_MB, DISTANCE_UNIT_KM }; gchar *dms; prefs.temperature_unit = priv->temperature_unit; prefs.speed_unit = priv->speed_unit; if (priv->weather_info) { weather_info_free (priv->weather_info); priv->weather_info = NULL; } if (priv->weather_timeout) { g_source_remove (priv->weather_timeout); priv->weather_timeout = 0; } if (!priv->weather_code || strcmp (priv->weather_code, WEATHER_EMPTY_CODE) == 0) return; dms = rad2dms (priv->latitude, priv->longitude); wl = weather_location_new (priv->city, priv->weather_code, NULL, NULL, dms, NULL, NULL); priv->weather_info = weather_info_new (wl, &prefs, weather_info_updated, loc); set_weather_update_timeout (loc); weather_location_free (wl); g_free (dms); } void clock_location_set_weather_prefs (ClockLocation *loc, WeatherPrefs *prefs) { ClockLocationPrivate *priv = PRIVATE (loc); priv->temperature_unit = prefs->temperature_unit; priv->speed_unit = prefs->speed_unit; update_weather_info (loc); }